在 Java 開發中,單例模式是最常用的設計模式之一。為了實現線程安全的延遲初始化單例,開發者經歷了多個版本的演進,其中「雙重檢查鎖定」曾被視為經典解決方案,但因 Java 記憶體模型的特性而在 JDK 1.5 之前存在嚴重缺陷。本文將層層剖析這一問題的本質,並展示正確的修正之道。
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
多線程環境下,多個線程可能同時通過 instance == null 檢查,導致創建多個實例。
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
雖然解決了線程安全,但每次調用都需獲取類鎖,實例創建後的後續訪問仍會阻塞,效能低落。
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查
synchronized (Singleton.class) { // 加鎖
if (instance == null) { // 第二次檢查
instance = new Singleton(); // 創建實例
}
}
}
return instance;
}
這個版本試圖只在首次創建時加鎖,但存在嚴重的指令重排問題。
instance = new Singleton() 並非原子操作,JVM 實際上會執行三步:
instance 引用由於編譯器與處理器優化,步驟 2 和 3 可能發生重排。當線程 A 執行完步驟 1 和 3 但尚未完成步驟 2 時,instance 已非 null 但對象未完全初始化。此時線程 B 進入第一次檢查,看到 instance != null 直接返回,獲得一個「半初始化」的對象,導致程式錯誤。
有人嘗試引入臨時變量與雙重同步塊:
public static Singleton getInstance() {
if (instance == null) {
Singleton temp;
synchronized (Singleton.class) {
temp = instance;
if (temp == null) {
synchronized (Singleton.class) {
temp = new Singleton();
}
instance = temp;
}
}
}
return instance;
}
企圖利用同步塊退出時的記憶體屏障防止重排。然而 Java 記憶體模型規定:同步塊內的操作必須在釋放鎖前完成,但同步塊之後的操作(如 instance = temp)允許被編譯器移入同步塊內執行,導致問題依然存在。
JDK 1.5 重新定義了 volatile 語義:
volatile 變量的寫入操作與其之前的任何讀寫操作重排volatile 變量的讀取操作與其之後的任何讀寫操作重排因此,只需將 instance 聲明為 volatile 即可修復雙重檢查鎖定:
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
注意:此方案僅在 JDK 1.5 及以上版本有效,JDK 1.4 及之前版本的 volatile 不具備此語義,雙重檢查鎖定仍然失效。
雙重檢查鎖定的失效本質是 Java 記憶體模型允許的指令重排導致「半初始化對象」洩露。JDK 1.5 引入的 volatile 語義增強從語言層面解決了此問題。實際開發中,推薦使用以下更簡潔、線程安全的單例實現:
Effective Java 作者 Joshua Bloch 推薦,天然防序列化破壞與反射攻擊理解雙重檢查鎖定的失效原因,有助於深入掌握 Java 並發編程的核心——可見性、有序性與原子性的協作關係。