double-checked locking、雙檢鎖、DCL機(jī)制還是存在線程安全的問題。
原因是new 操作不是原子操作,存在指令重排的問題
某一個線程在執(zhí)行到第一次檢測,讀取到的instance不為null時,instance的引用對象可能沒有完成初始化.
一、解決方法:volatile,禁止指令重排
最終版本的單例模式
public class Singleton {
// 1.持有自己類的屬性
private static volatile Singleton instance;
// 2.私有的構(gòu)造方法
private Singleton() {
}
// 3.提供對外獲取實(shí)例的方法
// DCL + volatile
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
// 因?yàn)?new 操作不是原子操作,所以需要對屬性加以volatile修飾,避免重排序
instance = new Singleton();
}
}
}
return instance;
}
}
二、再分析
DCL(雙端檢鎖) 機(jī)制不一定線程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排
原因在于某一個線程在執(zhí)行到第一次檢測,讀取到的instance不為null時,instance的引用對象可能沒有完成初始化.
instance=new SingletonDem(); 可以分為以下步驟(偽代碼)
memory=allocate();//1.分配對象內(nèi)存空間
instance(memory);//2.初始化對象
instance=memory;//3.設(shè)置instance的指向剛分配的內(nèi)存地址,此時instance!=null
步驟2和步驟3不存在數(shù)據(jù)依賴關(guān)系.而且無論重排前還是重排后程序執(zhí)行的結(jié)果在單線程中并沒有改變,因此這種重排優(yōu)化是允許的.
memory=allocate();//1.分配對象內(nèi)存空間
instance=memory;//3.設(shè)置instance的指向剛分配的內(nèi)存地址,此時instance!=null 但對象還沒有初始化完.
instance(memory);//2.初始化對象
但是指令重排只會保證串行語義的執(zhí)行一致性(單線程) 并不會關(guān)心多線程間的語義一致性
所以當(dāng)一條線程訪問instance不為null時,由于instance實(shí)例未必完成初始化,也就造成了線程安全問題.