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

Java 單例模式完整解析:懶漢、餓漢、雙重檢查鎖定與執行緒安全

     編輯:Java 相關知識

單例模式確保類別在 JVM 中僅有一個實例,並提供全域存取點。常見於執行緒池、快取、日誌、資料庫連線池等資源管理場景。本文系統性解析三種經典實作,並深入探討執行緒安全與效能權衡。

單例模式三大核心要素

  1. 私有建構子:防止外部 new 實例
  2. 私有靜態實例變數:持有唯一實例
  3. 公有靜態 getInstance():提供全域存取

1. 經典懶漢式(非執行緒安全)

public class Singleton {
    private static Singleton uniqueInstance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton(); // 多執行緒下可能建立多個實例
        }
        return uniqueInstance;
    }
}

缺點:並發環境下 uniqueInstance == null 判斷與賦值非原子操作,會產生多個實例。

2. 餓漢式(類別載入時初始化,天然執行緒安全)

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

優點:JVM 類別載入機制保證原子性,無同步開銷,效能最佳。
缺點:若實例建構耗時/資源大且未被使用,造成浪費;無法處理建構過程中的異常。

3. 靜態內部類(延遲載入 + 執行緒安全 + 效能佳)

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

原理:外部類別載入時不會初始化內部類,僅在首次呼叫 getInstance() 存取 Holder.INSTANCE 時觸發初始化,JVM 保證執行緒安全。推薦為預設選擇。

4. 雙重檢查鎖定 - DCL (JDK 1.5+ volatile 修正)

public class Singleton3 {
    private volatile static Singleton3 uniqueInstance; // 關鍵:禁止指令重排
    private Singleton3() {}
    public static Singleton3 getInstance() {
        if (uniqueInstance == null) { // 第一次檢查:無鎖讀取
            synchronized (Singleton3.class) { // 鎖定類別物件
                if (uniqueInstance == null) { // 第二次檢查:鎖內確認
                    uniqueInstance = new Singleton3();
                }
            }
        }
        return uniqueInstance;
    }
}

關鍵點:volatile 禁止「分配記憶體 → 建構物件 → 賦值引用」的指令重排,避免其他執行緒看到半初始化的物件。
適用場景:建構極其耗時、且極少呼叫 getInstance()、需極致延遲初始化。

5. 列舉實作 (Effective Java 推薦)

public enum Singleton4 {
    INSTANCE;
    public void doSomething() { /* ... */ }
}

優點:語法簡潔、免費提供序列化機制、絕對防止反射攻擊與多次初始化。最推薦(除非需延遲載入或繼承類別)。

執行緒安全深度解析

實作執行緒安全延遲載入效能備註
懶漢式 (無同步)❌✅最高不可用於生產環境
懶漢式 (同步方法)✅✅低每次呼叫皆同步,開銷大
餓漢式✅❌最高類別載入即初始化
靜態內部類✅✅高推薦
DCL + volatile✅✅高程式碼較複雜
Enum✅❌最高最推薦 (無需延遲時)

常見陷阱與補充

  • 反射攻擊:除 Enum 外,均可透過 Constructor.setAccessible(true) 強制建構。可在建構子檢查 if (INSTANCE != null) throw new IllegalStateException() 防禦。
  • 序列化破壞:實作 Serializable 時需加 readResolve() 回傳 INSTANCE,否則反序列化會產生新物件。
  • 類別載入器隔離:不同 ClassLoader 載入同一單例類別會產生多個實例(如 Tomcat 模組化部署)。

選型建議:
✅ 預設用 Enum (最簡單、最安全)
✅ 需延遲載入用 靜態內部類
⚠️ 極特殊效能需求才考慮 DCL
❌ 避免使用同步方法版懶漢式

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