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

Java 設計模式:單例模式深度解析與五種實作優劣比較

     編輯:Java 相關知識

單例模式是最基礎也最常被考察的創建型模式。本文系統性解析單例模式的三大動機、五種主流實作、執行緒安全本質、序列化/反射防禦,並提供生產環境選型決策表。

單例模式三大核心動機

  1. 控制實例數量:線程池、連線池、快取管理器、日誌器 — 僅允許一個協調中心
  2. 減少建構開銷:大型物件 (解析器、規則引擎) 重複建構消耗記憶體與 CPU
  3. 全域存取點:避免層層傳遞參數,提供統一服務入口

五種主流實作完整程式碼

1. 餓漢式 — 類別載入即初始化

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

2. 靜態內部類 — 延遲載入 + 執行緒安全 (推薦)

public final class SingletonHolder {
    private SingletonHolder() {}
    private static class Holder {
        static final SingletonHolder INSTANCE = new SingletonHolder();
    }
    public static SingletonHolder getInstance() { return Holder.INSTANCE; }

    // 序列化防禦
    protected Object readResolve() { return getInstance(); }
}

3. 雙重檢查鎖定 (DCL) — JDK 1.5+ volatile

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

4. 列舉實作 — Effective Java 首選

public enum SingletonEnum {
    INSTANCE;
    public void doBusiness() { /* ... */ }
}

5. 序列化安全單例 (補充)

public class SerializableSingleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final SerializableSingleton INSTANCE = new SerializableSingleton();
    private SerializableSingleton() {}
    public static SerializableSingleton getInstance() { return INSTANCE; }
    protected Object readResolve() { return INSTANCE; } // 關鍵:反序列化回傳單例
}

深度對比評估表

維度餓漢式靜態內部類DCLEnum
執行緒安全✅ (JVM 保證)✅ (JVM 保證)✅ (volatile)✅ (語言級)
延遲載入❌✅✅❌
效能 (獲取)極快 (直接讀取)極快 (直接讀取)快 (一次 volatile 讀取)極快
防反射攻擊❌❌❌✅ (語言保證)
防序列化破壞需 readResolve需 readResolve需 readResolve✅ (自動)
可繼承/實作介面✅✅✅❌ (不可繼承)
程式碼複雜度低低中極低

關鍵技術細節解析

為什麼 DCL 需要 volatile?

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

  1. 分配記憶體
  2. 初始化物件 (建構子)
  3. 將引用指向記憶體

無 volatile 時,JVM/CPU 可能重排為 1→3→2,導致其他執行緒看到「非 null 但未初始化完成」的物件。volatile 禁止此重排。

靜態內部類為何能延遲載入?

JVM 規範:類別首次主動使用時才初始化。外部類載入不觸發內部類初始化,僅在呼叫 Holder.INSTANCE 時觸發,且由 JVM 鎖定初始化過程,天然執行緒安全。

Enum 如何防反射?

java.lang.Enum 的 Constructor 為 private 且 JVM 內部特殊處理,Constructor.newInstance() 會拋出 IllegalArgumentException。

生產環境選型決策樹

需要延遲載入?
├── 否 → 需防反射/序列化破壞? → 是 → **Enum** (首選)
│                      └── 否 → **餓漢式** (最簡單)
└── 是 → 需繼承類別/實作介面? → 是 → **靜態內部類** (推薦)
                          └── 否 → 極致效能敏感? → **DCL** (罕見)
                               └── 否 → **靜態內部類** (推薦)

常見陷阱提醒

  • 多 ClassLoader 環境 (Tomcat、OSGi、模組化系統):每個 ClassLoader 會產生獨立單例,需指定共同父 ClassLoader 或使用 JNDI/模組系統管理共享實例
  • 序列化/反序列化:實作 Serializable 必須加 readResolve(),否則每次反序列化產生新實例
  • 建構子拋出異常:餓漢式/靜態內部類會導致類別初始化失敗 (ExceptionInInitializerError),後續無法重試;DCL/Enum 可在 getInstance() 中捕獲重試

一句话總結:
✅ 90% 場景用 Enum (最安全、最簡潔)
✅ 需延遲載入/繼承 → 靜態內部類
⚠️ 極罕見效能極限 → DCL + volatile
❌ 手寫同步方法/雙重檢查無 volatile → 錯誤範本

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