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

Java 單例模式七大寫法深度解析:從懶漢到枚舉的進化之路

     編輯:Java 程式設計入門

單例模式核心意圖

確保類別在全域僅有一個實例,並提供全域存取點。核心挑戰:執行緒安全、延遲載入、防止序列化/反射破壞、效能最佳化。

七大經典實作演進

1. 懶漢式(基礎版,非執行緒安全)

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

❌ 多執行緒並發會建立多個實例

2. 懶漢式同步方法(執行緒安全,效能差)

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

✅ 安全 ❌ 鎖粒度太大,實例化後每次呼叫都同步

3. 雙重檢查鎖(DCL,理論完美但指令重排有坑)

public static Singleton getInstance() {
    if (instance == null) {                    // First check
        synchronized (Singleton.class) {       // 類別級鎖
            if (instance == null) {            // Second check
                instance = new Singleton();    // 非原子操作!
            }
        }
    }
    return instance;
}

指令重排風險:instance = new Singleton() 分三步:①分配記憶體 ②初始化物件 ③指向記憶體。JVM 可能重排為 ①③②,導致其他執行緒見到「非 null 但未初始化完成」的實例。

4. volatile 修復 DCL(JDK 5+ 正確寫法)

private static volatile Singleton instance; // 禁止指令重排
public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();
            }
        }
    }
    return instance;
}

volatile 確保:①禁止指令重排 ②可見性(寫入對其他執行緒立即可見)

5. 靜態內部類(推薦:執行緒安全、延遲載入、效能高)

public class Singleton {
    private Singleton() {}
    private static class Holder {
        static final Singleton INSTANCE = new Singleton(); // 類別載入時初始化
    }
    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

✅ 利用類別載入機制天然執行緒安全 ✅ 外部類別載入時不初始化 Holder,達到延遲載入 ✅ 無同步開銷

6. 餓漢式(類別載入即建立,簡單但無延遲載入)

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() { return INSTANCE; }
}

✅ 簡單安全 ❌ 無延遲載入,未使用也佔記憶體 ❌ 無法依賴外部參數初始化

7. 枚舉單例(大師推薦:防序列化/反射破壞、最簡潔)

public enum Singleton {
    INSTANCE;
    public void doSomething() { /* 業務邏輯 */ }
}
// 使用:Singleton.INSTANCE.doSomething();

✅ JVM 保證單例 ✅ 天然防序列化破壞 ✅ 天然防反射破壞(Constructor 為 private 且枚舉特殊處理)✅ 程式碼最少

單例模式破壞與防禦

破壞手段一:序列化/反序列化


// 反序列化會建立新實例
ObjectInputStream ois = new ObjectInputStream(...);
Singleton s2 = (Singleton) ois.readObject();
s2 != Singleton.getInstance(); // true!

防禦:實作 readResolve() 回傳現有實例,或改用枚舉。

破壞手段二:反射暴力破解


Constructor<?> c = Singleton.class.getDeclaredConstructor();
c.setAccessible(true); // 突破 private
Singleton s2 = c.newInstance();
s2 != Singleton.INSTANCE; // true!

防禦:建構子中檢查 if (instance != null) throw Exception,或改用枚舉(JVM 封殺反射建立枚舉實例)。

選型建議決策表

場景推薦寫法理由
一般業務、需延遲載入靜態內部類安全、高效、延遲
極簡、需防序列化/反射枚舉最強防禦、程式碼最少
高併發、效能極致敏感volatile DCL雙重檢查最小化同步
類別載入即需使用、資源預熱餓漢式最簡單、無延遲需求

總結

單例模式看似簡單,實則蘊含執行緒安全、記憶體可見性、指令重排、類別載入機制、序列化、反射等核心 Java 知識。靜態內部類與枚舉是現代 Java 開發的雙首選,前者平衡延遲載入與效能,後者提供終極防禦。理解演進脈絡,才能在面試與架構設計中遊刃有餘。

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