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

Java 單例模式完整實現指南:七種寫法優劣對比與線程安全深度分析

     編輯:Java 相關知識

單例模式核心要求

  1. 全局唯一實例
  2. 自行創建實例 (私有構造器)
  3. 提供全局訪問點 (靜態工廠方法)
  4. 線程安全 (多線程環境下不創建多個實例)
  5. 性能優化 (避免每次調用加鎖)
  6. 防反射攻擊、防序列化破壞 (可選,視安全等級)

七種經典實現對比

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 synchronized Singleton2 getInstance() { // 整個方法同步
        if (instance == null) instance = new Singleton2();
        return instance;
    }
}
  • ✅ 延遲加載
  • ✅ 線程安全
  • ❌ 性能極差 (每次調用都加鎖,實際只需首次)

3. 雙重檢查鎖 (DCL) - 推薦高性能版


public class Singleton3 {
    private static volatile Singleton3 instance; // 關鍵: volatile 防止指令重排
    private Singleton3() {}
    public static Singleton3 getInstance() {
        if (instance == null) { // 第一次檢查 (無鎖,快速路徑)
            synchronized (Singleton3.class) { // 鎖類對象
                if (instance == null) { // 第二次檢查 (併發安全)
                    instance = new Singleton3();
                }
            }
        }
        return instance;
    }
}
  • ✅ 延遲加載
  • ✅ 高性能 (大多數情況無鎖)
  • ✅ 線程安全 (volatile 禁止指令重排,解決半初始化對象可見性問題)
  • ⚠️ JDK 1.5+ 才可靠 (JMM 修復 volatile 語義)

4. 靜態內部類 - 推薦最優雅版


public class Singleton4 {
    private Singleton4() {}
    // 靜態內部類,只有調用 getInstance() 時才會加載
    private static class Holder {
        static final Singleton4 INSTANCE = new Singleton4();
    }
    public static Singleton4 getInstance() {
        return Holder.INSTANCE;
    }
}
  • ✅ 延遲加載 (內部類首次被主動使用時初始化)
  • ✅ 線程安全 (類加載機制)
  • ✅ 代碼簡潔、無鎖、無 volatile 複雜性
  • 🏆 綜合最推薦

5. 枚舉單例 - 防反射/防序列化最強


public enum Singleton5 {
    INSTANCE;
    public void doSomething() { System.out.println("Enum Singleton"); }
}

// 使用: Singleton5.INSTANCE.doSomething();
  • ✅ 天然線程安全、延遲加載
  • ✅ 防反射攻擊 (構造器私有化由 JVM 強制)
  • ✅ 防序列化破壞 (enum 序列化機制特殊,保證單例)
  • ❌ 不支持延遲加載邏輯 (構造器無參數)、不靈活繼承

6. 容器管理 (Spring @Component + @Scope("singleton"))

現代項目交給 IOC 容器管理單例生命周期,非手動實現。

7. ThreadLocal 偽單例 (每線程一實例)


public class ThreadLocalSingleton {
    private static final ThreadLocal holder = 
        ThreadLocal.withInitial(ThreadLocalSingleton::new);
    private ThreadLocalSingleton() {}
    public static ThreadLocalSingleton getInstance() { return holder.get(); }
}
  • 用於線程隔離場景 (如數據庫連接、Session 上下文)

深度解析:為什麼 DCL 需要 volatile?

instance = new Singleton3() 非原子操作,JVM 可能重排為:

  1. 分配內存
  2. 賦值 instance 引用 (此時對象未初始化!)
  3. 調用構造器初始化對象

線程 A 執行到步驟 2,線程 B 進入第一次檢查看到 instance != null,直接返回半初始化對象 → 崩潰。volatile 禁止這種重排,保證構造器完成前引用不可見。

破壞單例的攻擊與防禦

攻擊手段原理防禦
反射調用私有構造器ctor.setAccessible(true); ctor.newInstance()構造器中檢查 if (INSTANCE != null) throw... 或用枚舉
反序列化readObject() 返回新實例實現 readResolve() 返回現有實例,或用枚舉
克隆實現 Cloneable 並 clone()不實現 Cloneable,或 clone 拋異常

防禦增強版 DCL


public class SecureSingleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static volatile SecureSingleton instance;
    
    private SecureSingleton() {
        // 防反射
        if (instance != null) {
            throw new IllegalStateException("單例已存在,禁止反射創建");
        }
    }
    
    public static SecureSingleton getInstance() {
        if (instance == null) {
            synchronized (SecureSingleton.class) {
                if (instance == null) {
                    instance = new SecureSingleton();
                }
            }
        }
        return instance;
    }
    
    // 防序列化破壞
    protected Object readResolve() {
        return instance;
    }
}

總結與選型建議

場景推薦方案理由
一般業務、工具類靜態內部類 (Singleton4)延遲加載、線程安全、代碼簡潔、無 volatile 陷阱
高併發熱點、極致性能DCL (Singleton3) 或 惡漢式 (Singleton1)無鎖讀取、JIT 友好
安全敏感、防反射/序列化枚舉 (Singleton5)JVM 級保證、最簡潔
Spring 項目@Component + @Scope("singleton")容器管理、生命周期、AOP 支持

單例模式看似簡單,實則蘊含 JMM 內存模型、類加載機制、反射/序列化破壞 等深層知識。理解各寫法優劣與適用邊界,是 Java 開發者進階的必修課。

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