double-checked locking、双检锁、DCL机制还是存在线程安全的问题。
原因是new 操作不是原子操作
,存在指令重排
的问题
某一个线程在执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化.
一、解决方法:volatile,禁止指令重排
最终版本的单例模式
public class Singleton {
// 1.持有自己类的属性
private static volatile Singleton instance;
// 2.私有的构造方法
private Singleton() {
}
// 3.提供对外获取实例的方法
// DCL + volatile
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
// 因为 new 操作不是原子操作,所以需要对属性加以volatile修饰,避免重排序
instance = new Singleton();
}
}
}
return instance;
}
}
二、再分析
DCL(双端检锁) 机制不一定线程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排
原因在于某一个线程在执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化.
instance=new SingletonDem(); 可以分为以下步骤(伪代码)
memory=allocate();//1.分配对象内存空间
instance(memory);//2.初始化对象
instance=memory;//3.设置instance的指向刚分配的内存地址,此时instance!=null
步骤2和步骤3不存在数据依赖关系.而且无论重排前还是重排后程序执行的结果在单线程中并没有改变,因此这种重排优化是允许的.
memory=allocate();//1.分配对象内存空间
instance=memory;//3.设置instance的指向刚分配的内存地址,此时instance!=null 但对象还没有初始化完.
instance(memory);//2.初始化对象
但是指令重排只会保证串行语义的执行一致性(单线程) 并不会关心多线程间的语义一致性
所以当一条线程访问instance不为null时,由于instance实例未必完成初始化,也就造成了线程安全问题.
参考:互联网大厂高频重点面试题