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

Java 單例模式五種寫法深度解析:從餓漢到枚舉的演進之路

     編輯:Java 綜合教學

Java 單例模式五種寫法深度解析:從餓漢到枚舉的演進之路

單例模式是面試必考、開發常用的創建型設計模式,核心要求:全域唯一實例、延遲初始化、執行緒安全、防序列化破壞、防反射攻擊。本文詳細剖析五種經典實現方式的原理、優缺點與適用場景,並給出現代 Java 最佳實踐建議。

一、模式一:餓漢式 (靜態常量) - 最簡單、最安全

public class Singleton {
    // 1. 私有構造器,防止外部 new
    private Singleton() {}

    // 2. 類載入時即創建實例 (JVM 保證執行緒安全)
    private static final Singleton INSTANCE = new Singleton();

    // 3. 公開靜態獲取方法
    public static Singleton getInstance() {
        return INSTANCE;
    }
}
維度評價
執行緒安全⭐⭐⭐⭐⭐ (JVM 類載入機制天然保證)
延遲加載❌ 類載入即創建,可能造成資源浪費
程式碼複雜度⭐⭐⭐⭐⭐ (最簡潔)
防反射/序列化❌ 需額外處理
適用場景類輕量、一定會被使用、無外部依賴

二、模式二:懶漢式 (同步方法) - 延遲但效能差

public class Singleton {
    private Singleton() {}
    private static Singleton instance = null;

    // synchronized 鎖住整個方法,效能極低
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
維度評價
執行緒安全⭐⭐⭐⭐⭐
延遲加載✅ 首次調用才創建
效能⭐ (每次調用都同步,實際只需首次)
適用場景極少調用、教學演示

三、模式三:雙重檢查鎖 (DCL) - 經典但易寫錯

public class Singleton {
    private Singleton() {}
    // volatile 關鍵:禁止指令重排序,保證可見性
    private static volatile Singleton instance = null;

    public static Singleton getInstance() {
        if (instance == null) {                    // 第一次檢查 (無鎖,高效)
            synchronized (Singleton.class) {       // 鎖類對象
                if (instance == null) {            // 第二次檢查 (鎖內,安全)
                    instance = new Singleton();    // 非原子操作:分配記憶體→初始化→賦值引用
                }
            }
        }
        return instance;
    }
}

關鍵點解析:

  • volatile:防止 instance = new Singleton() 指令重排序 (分配記憶體 → 賦值引用 → 初始化對象),避免其他執行緒看到半初始化對象
  • 雙重檢查:首次無鎖判斷提升效能,次次鎖內判斷保證安全
  • JDK 5+ 記憶體模型修正後才真正可靠
維度評價
執行緒安全⭐⭐⭐⭐ (需正確加 volatile)
延遲加載✅
效能⭐⭐⭐⭐ (首次同步,後續無鎖)
程式碼複雜度⭐⭐ (易漏 volatile、易寫錯鎖對象)

四、模式四:靜態內部類 - 官方推薦標準寫法

public class Singleton {
    private Singleton() {}

    // 靜態內部類,只有在調用 getInstance() 時才會被載入初始化
    private static class Holder {
        static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

原理:利用 JVM 類載入機制 — 外部類載入時不載入內部類,首次調用 getInstance() 觸發 Holder 類初始化,JVM 保證執行緒安全。

維度評價
執行緒安全⭐⭐⭐⭐⭐ (JVM 天然保證)
延遲加載✅ (首次調用才載入內部類)
效能⭐⭐⭐⭐⭐ (無同步開銷)
程式碼複雜度⭐⭐⭐⭐ (清晰易懂)
防反射/序列化❌ 需額外處理
推薦指數⭐⭐⭐⭐⭐ 生產環境首選

五、模式五:枚舉單例 - 最強、最完美 (Effective Java 作者推薦)

public enum Singleton {
    INSTANCE;

    // 可添加實例欄位、方法
    private String data;
    public void doSomething() { System.out.println("Working..."); }
    public String getData() { return data; }
    public void setData(String data) { this.data = data; }
}

// 使用方式
Singleton.INSTANCE.doSomething();

為什麼枚舉最強?

  1. 天然執行緒安全:JVM 保證枚舉常量單一實例化
  2. 天然防反射:Constructor.newInstance() 對枚舉類型拋出 IllegalArgumentException
  3. 天然防序列化破壞:枚舉序列化只輸出名稱,反序列化由 valueOf 還原,不走構造器
  4. 程式碼極簡:零樣板代碼
維度評價
執行緒安全⭐⭐⭐⭐⭐
延遲加載✅ (首次引用枚舉常量才初始化)
防反射⭐⭐⭐⭐⭐ (語言級保證)
防序列化⭐⭐⭐⭐⭐ (語言級保證)
缺點不支援延遲初始化邏輯 (如需配置參數)、不支援繼承

六、綜合對比與選型建議

寫法執行緒安全延遲加載效能防反射防序列化推薦場景
餓漢式✅❌極高❌❌輕量、必用、無依賴
同步懶漢✅✅極低❌❌教學、極少用
DCL✅ (需 volatile)✅高❌❌舊版專案維護
靜態內部類✅✅極高❌❌一般生產首選
枚舉✅✅極高✅✅最高安全要求

七、進階:防反射與防序列化補強 (非枚舉寫法)

// 防反射:構造器中檢查
private Singleton() {
    if (INSTANCE != null) {  // 或 Holder.INSTANCE
        throw new IllegalStateException("已存在實例,禁止反射創建");
    }
}

// 防序列化:實現 readResolve()
private Object readResolve() {
    return INSTANCE;  // 返回現有實例,忽略反序列化產生的新對象
}

八、Spring 中的單例 Bean 與設計模式單例區別

  • 設計模式單例:JVM 進程內 ClassLoader 範圍唯一
  • Spring 單例 Bean:ApplicationContext 容器範圍內唯一 (預設 scope=singleton)
  • 同一 JVM 多個 Spring 容器 → 多個單例 Bean 實例
  • Spring 管理的單例 Bean 非執行緒安全,需開發者自行保證無狀態或同步

現代 Java 最佳實踐:

  • 一般業務:靜態內部類 — 簡潔、安全、延遲、高效
  • 安全敏感/框架核心:枚舉 — 終極防禦
  • Spring 專案:交給 Spring 管理 — @Component + @Scope("singleton"),專注業務邏輯

單例模式看似簡單,實則蘊含 Java 記憶體模型、類載入機制、反射序列化機制等核心知識。深度理解五種寫法演進邏輯,不僅能應對面試,更能在架構設計中做出正確選擇。

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