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

Java 單例設計模式全解析:餓漢、懶漢、雙重檢查鎖與線程安全實戰

     編輯:Java 綜合教學

Java 單例設計模式全解析:餓漢、懶漢、雙重檢查鎖與線程安全實戰

什麼是單例模式

單例模式確保一個類在全局範圍內有且僅有一個實例,並提供全局訪問點。適用場景:窗口管理器、配置管理器、數據庫連接池、日誌記錄器等「全局唯一」資源。核心三要素:私有構造器、靜態實例引用、公開靜態獲取方法。

五種經典實現演進

1. 餓漢式(類加載時初始化)

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

優點:實現簡單,類加載機制天然保證線程安全。
缺點:無延遲加載,若實例從未使用會浪費內存;異常拋出導致類加載失敗。

2. 懶漢式(基礎版,非線程安全)

public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) instance = new Singleton(); // 多線程下會創建多個實例
        return instance;
    }
}

多線程並發進入 if 判斷導致多次實例化。

3. 懶漢式 + 同步方法(線程安全但效率低)

public static synchronized Singleton getInstance() { ... }

每次調用都加鎖,性能損耗大。

4. 懶漢式 + 同步塊(同樣低效)

public static Singleton getInstance() {
    synchronized (Singleton.class) { ... } // 鎖類對象,非 this
}

5. 雙重檢查鎖(DCL,推薦生產環境)

public class Singleton {
    private static volatile Singleton instance = null; // volatile 禁止指令重排
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {                    // 第一次檢查,無鎖讀取
            synchronized (Singleton.class) {       // 加鎖
                if (instance == null) {            // 第二次檢查
                    instance = new Singleton();    // 實例化
                }
            }
        }
        return instance;
    }
}

關鍵點:volatile 確保 new Singleton() 的三步操作(分配內存、初始化對象、賦值引用)不被重排序,防止其他線程看到「半初始化」對象。JDK 1.5+ 記憶體模型修復後 DCL 完全可靠。

其他線程安全單例寫法

靜態內部類(推薦,延遲加載 + 線程安全 + 簡潔)

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

利用類加載機制:外部類加載時不初始化內部類,首次調用 getInstance() 才觸發內部類加載初始化,天然線程安全。

枚舉單例(最簡潔,防反序列化破壞、防反射攻擊)

public enum Singleton { INSTANCE; }

《Effective Java》強力推薦,天然序列化安全、反射無法破壞構造器。

實戰對比:加單例前 vs 加單例後

// 普通類:創建兩個對象,屬性獨立
Person p1 = new Person(); p1.setName("Jack"); p1.setAge(18);
Person p2 = new Person(); p2.setName("David"); p2.setAge(20);
// 輸出:Jack:18 / David:20

// 單例類:獲取同一實例,後設置覆蓋前設置
Person p1 = Person.getInstance(); p1.setName("Jack"); p1.setAge(18);
Person p2 = Person.getInstance(); p2.setName("David"); p2.setAge(20);
// 輸出:David:20 / David:20  (同一對象,狀態被覆蓋)

選型建議

場景推薦寫法
一般業務、需延遲加載靜態內部類 / 雙重檢查鎖
高併發、性能敏感枚舉 / 靜態內部類
需防反射/反序列化破壞枚舉
簡單工具類、無延遲需求餓漢式

總結

單例模式看似簡單,實則考驗對 Java 內存模型、類加載機制、線程同步的深度理解。從餓漢到 DCL 再到枚舉,每種寫法都有其適用邊界。生產環境首選枚舉或靜態內部類,兼顧線程安全、延遲加載、防攻擊與代碼簡潔。切記:單例保證的是實例唯一性,不代表狀態線程安全,可變狀態仍需額外同步。

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