四种单例模式
其实在学Spring5源码的时候,就已经罗列了各种单例模式。在此,学习过 java.util.concurrent 包后,站在多线程的角度上分析比较经典的懒汉模式的双重锁校验(又称为DCL懒汉式(Double Check Lock))。
逐步进阶,基础版本
1 | private static LazySingleton singleton4; |
存在问题分析:多线程进行访问时,可能多个线程会同时进入if 的作用域中,那么就会创建多个 LazySingleton() 对象,导致这些线程返回的对象地址不一致。
普通加锁版本
直接方法加锁,确实能够解决问题,但是性能十分低下。
1 | private static LazySingleton singleton4; |
双重校验加锁版本
多线程都可以进入方法和第一个 if 的作用域,锁类的目的是保证只能有一个线程进入当前类后进入第二个if作用域创建实例对象,随后解锁,让其它进入第一个if作用域的阻塞线程在判断一次是否为空。
虽然性能是提升了,但是真的线程安全吗?并发下的指令重排是会出问题的,分析: singleton4 = new LazySingleton() 不是原子性操作,有三个步骤: ①分配内存空间 ②执行构造方法并实例化对象 ③ 分配内存地址,把这个对象指向这个空间。CPU执行时不一定是按照123执行的,如果按照执行顺序是132,第3步先执行的话,还没有完成实例化,就指向这个空间了,此时的 singleton4 不为空,下一个线程进入方法的第一个if判断走false,直接返回这个对象,此时的这个 singleton4 并没有完成实例化!所以就会导致数据不一致的问题。
volatile 有三大特性:1、可见性;2、禁止指令重排;3、不保证原子性。所以使用volatile 关键字修饰即可。
1 | private volatile static LazySingleton singleton4; |
单例模式被破坏的情况
破坏单例模式的意思就是:一个单例对象存在多个。
反射、序列化、克隆都会破坏单例模式。
解决方案:枚举单例模式、容器式单例模式(ConcurrentHashMap)、线程隔离式单例模式(ThreadLocal)
尝试破坏枚举单例模式:
1 | private enum Lazy4 { |