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

Java 單例模式全解析:七種寫法、線程安全、序列化破壞與最佳實踐

     編輯:Java 綜合教學

前言:為什麼單例模式是面試必考、開發必用?

單例模式確保類在全局僅有一個實例,並提供全局訪問點。它廣泛應用於線程池、緩存、日誌、配置管理、數據庫連接池等資源控制場景。本文系統梳理七種經典寫法,深度分析線程安全、性能、序列化/反射攻擊防禦,給出生產環境最佳選擇。

單例模式三大核心要素

  1. 私有構造器:防止外部 new
  2. 私有靜態實例變量:持有唯一實例
  3. 公有靜態獲取方法:全局訪問入口

七種寫法深度對比

1. 餓漢式(靜態常量)— 推薦基礎版

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() { return INSTANCE; }
}
  • ✅ 類加載時初始化,天然線程安全(JVM 保證)
  • ❌ 無延遲加載,若實例創建耗時且未使用會浪費資源

2. 餓漢式(靜態代碼塊)— 可處理異常

public class Singleton {
    private static final Singleton INSTANCE;
    static {
        try { INSTANCE = new Singleton(); }
        catch (Exception e) { throw new RuntimeException(e); }
    }
    // ...
}

3. 懶漢式(同步方法)— 不推薦

public static synchronized Singleton getInstance() {
    if (instance == null) instance = new Singleton();
    return instance;
}
  • ✅ 延遲加載、線程安全
  • ❌ 效率極低,每次調用都加鎖

4. 懶漢式(雙重檢查鎖 DCL)— 經典但易寫錯

public static Singleton getInstance() {
    if (instance == null) {                    // 第一次檢查
        synchronized (Singleton.class) {       // 加鎖
            if (instance == null) {            // 第二次檢查
                instance = new Singleton();    // 關鍵:需 volatile
            }
        }
    }
    return instance;
}
// 必須加 volatile 防止指令重排序!
private static volatile Singleton instance;

為什麼需要 volatile? new Singleton() 非原子操作:分配內存 → 初始化對象 → 指向引用。重排序可能導致其他線程看到「已分配但未初始化」的對象。

5. 靜態內部類— 推薦生產版

public class Singleton {
    private Singleton() {}
    private static class Holder {
        static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() { return Holder.INSTANCE; }
}
  • ✅ 延遲加載(外部類加載不初始化內部類)
  • ✅ 線程安全(JVM 類加載機制保證)
  • ✅ 代碼簡潔,無鎖高性能
  • ✅ 防止反射攻擊(構造器私有,但可通過 setAccessible(true) 破壞)

6. 枚舉單例— 最強防禦版(Effective Java 推薦)

public enum Singleton {
    INSTANCE;
    public void doSomething() { /* ... */ }
}
  • ✅ 天然線程安全、延遲加載
  • ✅ 防止反射攻擊(JVM 禁止枚舉構造器被反射調用)
  • ✅ 防止序列化破壞(JVM 保證枚舉單例序列化不創建新對象)
  • ❌ 不支援延遲加載邏輯(如需配置參數初始化較麻煩)

7. 容器管理單例— Spring @Bean/@Scope("singleton")

現代框架由 IOC 容器管理生命週期,本質是「註冊式單例」。

破壞單例的兩大殺手與防禦

攻擊方式破壞原理防禦方案
反射Constructor.setAccessible(true) 調用私有構造器枚舉單例 / 構造器中拋異常(如 if (INSTANCE != null) throw...)
序列化/反序列化readObject() 默認創建新實例實現 readResolve() 返回現有實例 / 使用枚舉

生產環境選型建議

場景推薦寫法理由
通用業務單例靜態內部類延遲加載+線程安全+簡潔高性能
安全敏感/防破壞枚舉單例JVM 級防反射、防序列化破壞
需配置參數初始化DCL + volatile可在 getInstance 內處理複雜初始化邏輯
Spring 管理 Bean@Scope("singleton")容器統一生命週期,支援 AOP、依賴注入

總結

單例模式看似簡單,實則蘊含「類加載機制」、「內存模型」、「反射/序列化機制」等核心知識。靜態內部類是通用首選,枚舉是安全首選。理解 volatile 在 DCL 中的作用,更能加深對 JMM happens-before 原則的掌握。

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