public class Singleton {
//懶漢模式 雙重檢查鎖定DCL(double-checked locking)
//缺點:由於jvm存在亂序執行功能,DCL也會出現線程不安全的情況。(DCL失效問題)
// jdk1.6及之后,只要定義為private volatile static SingleTon instance 就可解決DCL失效問題。
// volatile確保instance每次均在主內存中讀取,這樣雖然會犧牲一點效率,但也無傷大雅。
// volatile可以保證即使java虛擬機對代碼執行了指令重排序,也會保證它的正確性。
private volatile static Singleton instance; //延遲加載,需要時才創建實例
private Singleton() { //私有化構造函數
}
public static Singleton getInstance() {
if (instance == null) { //只有第一次調用時,才需要同步判斷(此時instance未初始化,為null)。一旦instance初始化完成,就不需要了
synchronized (Singleton.class) { //只要鎖住new即可,不需要放在外面getInstance()方法上
if (instance == null) { //二次判斷,為了避免線程一在創建實例之后,線程二進來也創建了新實例
instance = new Singleton();
}
}
}
return instance;
}
}
DCL及解決辦法 說明:
針對延遲加載法的同步實現所產生的性能低的問題,可以采用DCL,即雙重檢查加鎖(Double Check Lock)的方法來避免每次調用getInstance()方法時都同步。
Double-Checked Locking看起來是非常完美的。但是很遺憾,根據Java的語言規范,上面的代碼是不可靠的。
出現上述問題, 最重要的2個原因如下:
1, 編譯器優化了程序指令, 以加快cpu處理速度.
2, 多核cpu動態調整指令順序, 以加快並行運算能力.
問題出現的順序:
1, 線程A, 發現對象未實例化, 准備開始實例化
2, 由於編譯器優化了程序指令, 允許對象在構造函數未調用完前, 將共享變量的引用指向部分構造的對象, 雖然對象未完全實例化, 但已經不為null了.
3, 線程B, 發現部分構造的對象已不是null, 則直接返回了該對象.
解決辦法:
可以將instance聲明為volatile,即 private volatile static Singleton instance
在線程B讀一個volatile變量后,線程A在寫這個volatile變量之前,所有可見的共享變量的值都將立即變得對線程B可見。
更深入理解DCL原理,請查看:Java內存模型 https://blog.csdn.net/wuzhiwei549/article/details/80006533?utm_source=copy