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

Java 單例模式深度解析:從懶漢到雙重檢查鎖的演進

     編輯:Java 相關知識

單例模式核心意圖

確保類在全局範圍內僅有一個實例,並提供統一訪問入口。關鍵約束:構造器私有化、靜態工廠方法、防止反射/序列化破壞(本文未涉及)。

兩大基本實現路線

維度餓漢式懶漢式
初始化時機類加載階段(靜態初始化)首次調用 getInstance()
資源佔用可能造成不必要的早期佔用按需分配,節省資源
線程安全天然安全(JVM 保證類初始化原子性)需額外同步機制
適用場景實例創建輕量、必然被使用創建開銷大、可能不被使用

經典實現演進

1. 基礎餓漢式

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

2. 基礎懶漢式(線程不安全)

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

併發缺陷:多線程同時通過 instance == null 判斷,導致多次實例化。

3. 同步方法版(性能差)

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

每次調用均同步,嚴重影響高併發性能。

4. 雙重檢查鎖 (DCL) + volatile(推薦)

public class Singleton4 {
    private static volatile Singleton4 instance;
    private Singleton4() {}
    public static Singleton4 getInstance() {
        if (instance == null) {                    // 第一次檢查(無鎖)
            synchronized (Singleton4.class) {      // 加鎖
                if (instance == null) {            // 第二次檢查(鎖內)
                    instance = new Singleton4();   // 實例化
                }
            }
        }
        return instance;
    }
}

關鍵技術點深度剖析

為什麼需要 volatile?

instance = new Singleton4() 非原子操作,實際分三步:分配內存、初始化對象、引用賦值。JVM 指令重排序可能導致「引用賦值」先於「初始化完成」,其他線程看到非 null 引用卻訪問到半初始化對象。volatile 禁止指令重排序,保證「寫入內存可見性」與「有序性」。

volatile vs synchronized 成本對比

  • synchronized:鎖升級(偏向→輕量→重量)、上下文切換、內存屏障,開銷大
  • volatile:僅變量級內存屏障,無線程阻塞,吞吐量優勢明顯
  • JDK 1.5+ 修復了 volatile 語義,始終建議使用

DCL 失效歷史與現狀

JDK 1.4 及之前:volatile 無法禁止重排序,DCL 失效。
JDK 1.5+:JSR-133 強化 volatile 語義,DCL 完全可用。

其他高級實現(補充)

靜態內部類(推薦替代 DCL)

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

利用類加載機制天然線程安全、延遲初始化、代碼簡潔,無 volatile 複雜度。

枚舉單例(Effective Java 推薦)

public enum Singleton6 {
    INSTANCE;
    public void doSomething() {}
}

天然防序列化破壞、防反射攻擊、線程安全,但無法延遲加載(枚舉類加載即實例化)。

選型建議

場景推薦方案理由
通用業務、輕量對象餓漢式 / 靜態內部類簡單可靠,無併發風險
重資源對象、可能不使用靜態內部類 / DCL延遲加載 + 高性能
需防序列化/反射破壞枚舉單例語言級保證
框架級組件、需擴展性Spring 容器管理 @Scope("singleton")統一生命週期、AOP 支持

總結

單例模式看似簡單,實則蘊含 Java 內存模型、類加載機制、併發控制等核心知識。現代開發中,靜態內部類與枚舉是最佳平衡點;DCL 適合需精確控制初始化邏輯的場景。切記:業務代碼優先考慮依賴注入框架管理單例,而非手寫模式。

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