一、单例模式(Singleton)
1、单例模式应用场景
①Servlet
②任务管理器
③链接池
④Spring中每个 bean 默认是单例
⑤网站计数器
2、单例要求
①构造器私有
②私有的静态变量
③公共的静态的可以访问私有的静态变量的方法
结论:由结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象。
二、单例模式的实现方式
1、饿汉式
线程安全、立即加载、资源利用率低、调用效率高
1 package cn.com.zfc.gof01.singleton; 2 3 /** 4 * 5 * @title Singleton01 6 * @describe 饿汉式实现单例模式 7 * @author 张富昌 8 * @date 2017年3月27日上午8:40:02 9 */ 10 public class Singleton01 { 11 12 // 天然的线程安全的,类加载是立即加载这个实例 13 private static Singleton01 instance = new Singleton01(); 14 15 / 构造器私有化 16 private Singleton01() { 17 18 } 19 20 // 方法没有同步,效率比较高 21 public static Singleton01 getInstance() { 22 return instance; 23 } 24 }
2、懒汉式
线程安全、延迟加载、资源利用率高、调用效率低
1 package cn.com.zfc.gof01.singleton; 2 3 /** 4 * 5 * @title Singleton02 6 * @describe 懒汉式实现单例模式 7 * @author 张富昌 8 * @date 2017年3月27日上午8:45:42 9 */ 10 public class Singleton02 { 11 12 private static Singleton02 instance = null; 13 14 //构造其私有化 15 private Singleton02() { 16 } 17 18 //公共,同步,静态 19 public static synchronized Singleton02 getInstance() { 20 if (instance == null) { 21 instance = new Singleton02(); 22 } 23 return instance; 24 } 25 }
3、双重检索式
线程安全、延迟加载、资源利用率高、调用效率高、但不稳定
1 package cn.com.zfc.gof01.singleton; 2 3 /** 4 * 5 * @title Singleton03 6 * @describe 双重检测锁式实现单例模式(不建议使用) 7 * @author 张富昌 8 * @date 2017年3月27日上午8:52:59 9 */ 10 public class Singleton03 { 11 12 private static Singleton03 instance = null; 13 14 private Singleton03() { 15 16 } 17 18 public static Singleton03 getInstance() { 19 if (instance == null) { 20 Singleton03 sc; 21 synchronized (Singleton03.class) { 22 sc = instance; 23 if (sc == null) { 24 synchronized (Singleton03.class) { 25 if (sc == null) { 26 sc = new Singleton03(); 27 } 28 } 29 instance = sc; 30 } 31 } 32 } 33 return instance; 34 } 35 36 }
4、静态内部类式(相比于懒汉式更好)
线程安全、延迟加载、资源利用率高、调用效率高
1 package cn.com.zfc.gof01.singleton; 2 3 /** 4 * 5 * @title Singleton04 6 * @describe 静态内部类式实现单例模式 7 * @author 张富昌 8 * @date 2017年3月27日上午8:54:40 9 */ 10 public class Singleton04 { 11 12 // 构造器私有化 13 private Singleton04() { 14 15 } 16 17 // 静态内部类 18 private static class StaticInnerClass { 19 private static final Singleton04 INSTANCE = new Singleton04(); 20 } 21 22 public static Singleton04 getInstance() { 23 return StaticInnerClass.INSTANCE; 24 } 25 26 }
5、枚举单例式(相比于饿汉式更好)
线程安全、立即加载、可以天然的防止反射和反序列化
1 package cn.com.zfc.gof01.singleton; 2 3 /** 4 * 5 * @title Singleton05 6 * @describe 枚举式实现单例模式 7 * @author 张富昌 8 * @date 2017年3月27日上午9:01:59 9 */ 10 public enum Singleton05 { 11 // 单例对象 12 INSTANCE; 13 14 // 如果要对该单例对象进行额外的操作,则加入方法 15 public void singlrtonOperation() { 16 17 } 18 19 }
三、测试多线程环境下五种创建单例模式的效率
1 package cn.com.zfc.gof01.singleton.test; 2 3 import java.util.concurrent.CountDownLatch; 4 5 import cn.com.zfc.gof01.singleton.Singleton05; 6 7 /** 8 * 9 * @title SingletonTest 10 * @describe 测试多线程环境下五种创建单例模式的效率 11 * @author 张富昌 12 * @date 2017年3月27日上午9:24:58 13 */ 14 public class SingletonTest { 15 public static void main(String[] args) throws Exception { 16 17 long start = System.currentTimeMillis(); 18 int threadNum = 10; 19 // A synchronization aid that allows one or more threads to wait until a 20 // set of operations being performed in other threads completes. 21 // 计数器(一个线程执行完成就减1) 22 final CountDownLatch countDownLatch = new CountDownLatch(threadNum); 23 24 for (int i = 0; i < threadNum; i++) { 25 // 多线程测试 26 new Thread(new Runnable() { 27 @Override 28 public void run() { 29 30 for (int i = 0; i < 1000000; i++) { 31 // 饿汉式, 32 // Object o = Singleton01.getInstance(); 33 // 懒汉式,最慢 34 // Object o = Singleton02.getInstance(); 35 // 双重检测锁式,不稳定,不建议使用 36 // Object o = Singleton03.getInstance(); 37 // 静态内部类 38 // Object o = Singleton04.getInstance(); 39 // 枚举式 40 Object o = Singleton05.INSTANCE; 41 } 42 43 // 一个线程执行完成就减1 44 countDownLatch.countDown(); 45 } 46 }).start(); 47 } 48 49 // 阻塞 50 countDownLatch.await(); // main线程阻塞,直到计数器变为0,才会继续往下执行! 51 52 long end = System.currentTimeMillis(); 53 System.out.println("总耗时:" + (end - start)); 54 } 55 }
四、什么是线程安全?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。