首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

線程安全單例模式 4 大寫法深度對比:從餓漢到雙重檢查鎖

     編輯:Java 相關知識

寫法一:餓漢式 (類加載即創建)

public class Singleton {
    private final static Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() { return INSTANCE; }
}
  • JVM 類加載機制保證線程安全;
  • 無懶加載,類加載即佔用內存。

寫法二:靜態內部類 (懶加載 + 無鎖)

public class Singleton {
    private Singleton() {}
    private static class Holder {
        private final static Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() { return Holder.INSTANCE; }
}
  • 外部類加載不初始化內部類;
  • 首次調用 getInstance() 觸發內部類加載 → 初始化 INSTANCE;
  • **推薦為通用首選**。

寫法三:同步方法 (簡單但低效)

public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static synchronized Singleton getInstance() {
        if (instance == null) instance = new Singleton();
        return instance;
    }
}
  • 每次調用都加鎖,高併發下性能差;
  • 僅作教學演示。

寫法四:雙重檢查鎖 (DCL) — 細節決定成敗

❌ 錯誤寫法 (指令重排序導致半初始化對象)

public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) instance = new Singleton(); // 危險!
        }
    }
    return instance;
}

✅ 正確寫法 A:volatile 禁止重排序


private static volatile Singleton instance;
public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) instance = new Singleton();
        }
    }
    return instance;
}

✅ 正確寫法 B:局部變量臨時存儲 (無需 volatile)


public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            Singleton temp = instance;        // 讀入局部變量
            if (temp == null) {
                temp = new Singleton();
                instance = temp;              // 寫入 volatile/共享變量
            }
        }
    }
    return instance;
}

原理深度:為什麼需要 volatile?

instance = new Singleton() 非原子,分三步:

  1. 分配內存;
  2. 初始化對象;
  3. 引用指向內存。

JIT 編譯器可能重排為 1→3→2,導致其他線程看到非 null 但未初始化完全的對象。volatile 插內存屏障禁止重排序;局部變量寫法利用「寫入共享變量前已完全初始化」規避問題。

選型建議

場景推薦
通用業務、追求簡潔靜態內部類
極致性能、手動控制初始化時機DCL + volatile
框架級、需防反射/序列化破壞枚舉 (見上文)
實例極輕量、確定會用餓漢式

掌握 DCL 細節,是 Java 並發編程進階的分水嶺。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved