// 非線程安全
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
}
多線程並發調用 getInstance() 可能創建多個實例。
public static synchronized Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
每次調用都加鎖,單例創建後仍同步,吞吐量極低。
public static Singleton getInstance() {
if (instance == null) { // 1. 第一次檢查
synchronized (Singleton.class) { // 2. 加鎖
if (instance == null) { // 3. 第二次檢查
instance = new Singleton(); // 4. 初始化
}
}
}
return instance;
}
缺陷根源:instance = new Singleton() 非原子操作,JVM 可能重排序為:
memory = allocate() 分配內存instance = memory 引用指向內存 (對象尚未初始化!)ctorInstance(memory) 執行構造函數初始化線程 A 執行到步驟 2 時,線程 B 進入第一次檢查看到 instance != null,直接返回未初始化完成的對象 → 空指針/數據不一致。
private static volatile Singleton instance; // 關鍵:volatile
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // volatile 保證:寫入 instance 前構造函數必須完成
}
}
}
return instance;
}
原理:volatile 的 寫屏障 禁止指令重排序,確保「構造函數執行完成」 happens-before 「引用賦值給 instance」,其他線程讀取到非 null 時對象必然完全初始化。
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton(); // 類加載時初始化,JVM 保證線程安全
}
public static Singleton getInstance() {
return Holder.INSTANCE; // 首次調用觸發 Holder 類加載
}
}
優勢:
Singleton 類加載時不初始化 INSTANCE,只有調用 getInstance() 訪問 Holder.INSTANCE 時才觸發 Holder 初始化。Class 對象關聯的鎖,獲取者繼續,其他線程等待。<clinit>() (靜態字段賦值、靜態代碼塊),期間其他線程在 Condition 上等待。initialized,喚醒等待線程。initialized 直接返回;新線程獲取鎖讀到 initialized 直接返回。根據 JMM 鎖規則:unlock → lock 構成 happens-before,保證初始化線程的寫入 (對象構造) 對後續線程可見。
| 方案 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| synchronized 方法 | 低併發、初始化極快 | 簡單 | 性能差 |
| volatile DCL | 實例字段延遲初始化、需參數構造 | 支持實例字段、靈活 | 代碼較複雜、易寫錯 |
| 靜態內部類 | 靜態字段單例、無參構造 | 無鎖、JVM 保證安全、代碼極簡 | 不支持實例字段、構造需無參 |
volatile 寫屏障、類初始化鎖、JMM happens-before 是掌握並發編程的核心鑰匙。