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

Java 單例模式終極指南:7 種寫法、線程安全、破壞與防禦

     編輯:Java 相關知識

單例模式核心意圖

確保某個類在**全局(JVM/類加載器範圍)僅有一個實例**,並提供全局訪問點。適用於:資源共享(連接池、線程池、緩存)、控制資源併發訪問、配置管理、日誌記錄器等。

七種經典實現:從入門到精通

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;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) instance = new Singleton(); // 多線程下會創建多個
        return instance;
    }
}

✅ 延遲加載 ❌ **線程不安全**,高併發下失效。

3. 雙重檢查鎖(DCL,JDK 5+ volatile 修復指令重排)

public class Singleton {
    private static volatile Singleton instance; // 關鍵:volatile 禁止指令重排
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {                    // 第一次檢查,無鎖讀取
            synchronized (Singleton.class) {       // 加鎖
                if (instance == null) {            // 第二次檢查
                    instance = new Singleton();    // 實例化(非原子操作:分配內存→初始化→賦值引用)
                }
            }
        }
        return instance;
    }
}

✅ 延遲加載、高併發性能好 ⚠️ **JDK 5 前 volatile 語義缺陷導致 DCL 失效**,現代 JDK 安全。但代碼較繁瑣。

4. 靜態內部類(推薦:延遲加載 + 線程安全 + 代碼簡潔)

public class Singleton {
    private Singleton() {}
    private static class Holder {
        static final Singleton INSTANCE = new Singleton(); // 類加載時初始化,JVM 保證線程安全
    }
    public static Singleton getInstance() { return Holder.INSTANCE; }
}

✅ 結合餓漢線程安全 + 懶漢延遲加載,**最推薦手寫版本**。利用類加載機制:外部類加載時不加載內部類,首次調用 getInstance() 觸發 Holder 初始化。

5. 枚舉單例(最強:防序列化破壞 + 防反射破壞 + 線程安全)

public enum Singleton {
    INSTANCE;
    public void doSomething() { /* 業務邏輯 */ }
}
// 使用:Singleton.INSTANCE.doSomething();

✅ **《Effective Java》強力推薦**。枚舉天然單例,JVM 保證:
① 序列化時只序列化枚舉常量名,反序列化由 Enum.valueOf 保證唯一。
② 構造函數私有,反射 Constructor.setAccessible(true) 會拋異常。
③ 類加載機制天然線程安全。

6. 容器管理單例(Spring @Component/@Bean、EJB @Singleton)

框架層面保證單例,非代碼層面實現,此處不展開。

單例模式的兩大「破壞者」與防禦

1. 反射破壞(枚舉除外)

Constructor<Singleton> c = Singleton.class.getDeclaredConstructor();
c.setAccessible(true);
Singleton s1 = Singleton.getInstance();
Singleton s2 = c.newInstance(); // 創建第二個實例!
System.out.println(s1 == s2); // false

防禦:在構造函數中檢查實例是否已存在:

private Singleton() {
    if (INSTANCE != null) { // 或 Holder.INSTANCE
        throw new IllegalStateException("Singleton already initialized");
    }
}

2. 反序列化破壞(枚舉除外)

// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("s.obj"));
oos.writeObject(Singleton.getInstance());
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("s.obj"));
Singleton s2 = (Singleton) ois.readObject(); // 新實例!

防禦:實現 readResolve() 返回現有實例:

private Object readResolve() { return INSTANCE; } // 或 Holder.INSTANCE

實戰選型決策表

場景推薦方案理由
一般業務、工具類靜態內部類代碼少、延遲加載、線程安全、無 volatile 複雜度
高併發核心組件、框架底層枚舉單例極致防禦、零鎖開銷、JVM 級保證
需延遲加載且 JDK 5 以下環境同步方法/塊犧牲性能換安全,`synchronized getInstance()`
Spring 管理 Bean@Component + @Scope("singleton")容器托管,非代碼層面實現

常見誤區與避坑

  • ❌ public static Singleton getInstance() { return new Singleton(); } — 每次調用新建對象,非單例。
  • ❌ 單例中持有非線程安全狀態(如 SimpleDateFormat、ArrayList) — 單例只保證實例唯一,**不保證線程安全**,需自行同步或用 ThreadLocal。
  • ❌ 過度使用單例 — 導致全局狀態耦合、難測試、違反單一職責。優先考慮依賴注入管理生命週期。

總結

單例模式看似簡單,實則蘊含「類加載機制、內存模型、反射、序列化」等核心知識。**靜態內部類**是手寫代碼的最佳平衡點;**枚舉**是極致防禦的終極形態。實戰中:能用依賴注入框架管理生命週期就不用手寫單例;必須手寫時,首選枚舉,次選靜態內部類,並根據場景決定是否加反射/序列化防禦。

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