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

Java 單例模式深度解析:七種寫法優劣與線程安全實戰

     編輯:Java 程式設計入門

單例模式確保類別在 JVM 中僅有一個實例,並提供全域存取點。本文系統梳理七種經典寫法,從懶漢、餓漢到雙重檢查鎖、靜態內部類、枚舉,對比線程安全、延遲載入、序列化破壞、反射攻擊等關鍵指標,給出生產環境選型建議。

核心要求檢核表

  • [ ] 唯一實例:全 JVM 僅一個對象。
  • [ ] 全域存取:提供靜態 getInstance()。
  • [ ] 線程安全:多執行緒併發調用不產生多實例。
  • [ ] 延遲載入:首次使用時才建立 (非強制,但利於資源釋放)。
  • [ ] 防反射破壞:Constructor.setAccessible(true) 不能繞過。
  • [ ] 防序列化破壞:反序列化不產生新實例。

七種寫法完整對比

1. 餓漢式 (Eager Initialization) ⭐⭐⭐⭐

public class Emperor {
    private static final Emperor INSTANCE = new Emperor();
    private Emperor() {}
    public static Emperor getInstance() { return INSTANCE; }
}
  • ✅ 線程安全 (類載入機制保證)
  • ✅ 程式碼最簡潔
  • ❌ 無延遲載入,類載入即佔用記憶體
  • ❌ 可被反射/序列化破壞

2. 懶漢式同步方法 (Synchronized Method) ⭐⭐

public class Emperor {
    private static Emperor instance;
    private Emperor() {}
    public static synchronized Emperor getInstance() {
        if (instance == null) instance = new Emperor();
        return instance;
    }
}
  • ✅ 延遲載入、線程安全
  • ❌ 效能極差:每次調用皆同步,實際只需首次同步
  • ❌ 可被反射/序列化破壞

3. 雙重檢查鎖 (Double-Checked Locking) ⭐⭐⭐⭐

public class Emperor {
    private static volatile Emperor instance; // 關鍵:volatile 禁止指令重排
    private Emperor() {}
    public static Emperor getInstance() {
        if (instance == null) {                    // 第一次檢查 (無鎖)
            synchronized (Emperor.class) {         // 鎖定類對象
                if (instance == null) {            // 第二次檢查 (有鎖)
                    instance = new Emperor();
                }
            }
        }
        return instance;
    }
}
  • ✅ 延遲載入、高效能 (僅首次同步)
  • ✅ Java 5+ volatile 語義修復 DCL 問題
  • ❌ 程式碼較複雜
  • ❌ 可被反射/序列化破壞

4. 靜態內部類 (Static Inner Class) ⭐⭐⭐⭐⭐ 推薦

public class Emperor {
    private Emperor() {}
    private static class Holder {
        static final Emperor INSTANCE = new Emperor();
    }
    public static Emperor getInstance() { return Holder.INSTANCE; }
}
  • ✅ 延遲載入 (外部類載入不初始化內部類)
  • ✅ 線程安全 (類載入機制)
  • ✅ 程式碼簡潔、無同步開銷
  • ❌ 可被反射/序列化破壞

5. 枚舉單例 (Enum Singleton) ⭐⭐⭐⭐⭐ 最強推薦

public enum Emperor {
    INSTANCE;
    public void getName() { System.out.println("我是皇帝:明日科技"); }
}
// 使用: Emperor.INSTANCE.getName();
  • ✅ 天然線程安全、延遲載入
  • ✅ 防反射:JVM 禁止 Constructor.newInstance() 建立枚舉
  • ✅ 防序列化:JVM 保證反序列化返回同一枚舉常量
  • ✅ 程式碼極簡
  • ❌ 無法延遲載入到「首次呼叫方法」 (類載入即建立),但通常可接受
  • ❌ 無法繼承其他類 (但可實作介面)

6. ThreadLocal 單例 (每執行緒單例) ⭐⭐

public class Emperor {
    private static final ThreadLocal TL = ThreadLocal.withInitial(Emperor::new);
    private Emperor() {}
    public static Emperor getInstance() { return TL.get(); }
}
  • 適用場景:每個執行緒需要獨立實例 (如資料庫連線、Session 上下文)。
  • 非傳統單例,JVM 中存在多實例。

7. 容器管理單例 (Spring @Component / @Singleton) ⭐⭐⭐⭐

@Component
@Scope("singleton") // 預設即為 singleton
public class Emperor { ... }
  • 企業級開發首選:生命週期由容器管理、支援 AOP、依賴注入、優雅關閉。
  • 非純 Java 模式,但實際專案最常用。

選型決策樹

是否需防反射/序列化破壞?
  ├─ 是 → Enum Singleton (單一類) 或 Spring @Component (企業級)
  └─ 否
       ├─ 需延遲載入且極簡 → Static Inner Class
       ├─ 需高效能併發 → DCL (volatile)
       └─ 簡單工具類、無狀態 → Eager Initialization

生產環境防護清單

  1. 私有建構子:private Emperor() { if (INSTANCE != null) throw new IllegalStateException(); } (枚舉自動處理)。
  2. 實作 Serializable 時:加入 readResolve() 返回現有實例。
  3. 反射攻擊測試:單元測試驗證 Constructor.setAccessible(true) 拋出異常 (枚舉) 或返回同一實例 (內部類 + 建構子檢查)。
  4. 類載入器隔離場景:多模組/OSGI 環境下,靜態欄位可能每 ClassLoader 一份,需用父 ClassLoader 或 SPI 機制統一。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved