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

Java 單例模式五種寫法深度解析:從懶漢到雙重檢查鎖

     編輯:Java 相關知識

單例模式確保類全局唯一實例,是創建型模式中最基礎、面試最高頻的設計模式。本文系統梳理五種經典實現,從線程不安全到線程安全、從急切創建到延遲加載,層層遞進,並深度解析雙重檢查鎖(DCL)的原理與 volatile 關鍵作用。

一、五種實現方式對比

模式線程安全延遲加載性能複雜度推薦度
1. 懶漢式(基礎版)❌✅高低⭐
2. 懶漢式(同步方法)✅✅低低⭐⭐
3. 餓漢式✅❌高低⭐⭐⭐
4. 靜態內部類✅✅高中⭐⭐⭐⭐
5. 雙重檢查鎖 (DCL)✅✅高高⭐⭐⭐⭐⭐

二、代碼實現詳解

1. 懶漢式基礎版(線程不安全)

public class Singleton1 {
    private static Singleton1 instance;
    private Singleton1() {}
    public static Singleton1 getInstance() {
        if (instance == null) {  // 多線程並發時可能創建多個實例
            instance = new Singleton1();
        }
        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. 餓漢式(類加載時創建)

public class Singleton3 {
    // 類加載機制天然保證線程安全
    private static final Singleton3 INSTANCE = new Singleton3();
    private Singleton3() {}
    public static Singleton3 getInstance() {
        return INSTANCE;
    }
}
缺點:類加載即創建,若實例佔用資源大且未必用到,造成浪費。

4. 靜態內部類(推薦:完美平衡)

public class Singleton4 {
    private Singleton4() {}

    // 內部類只有在調用 getInstance() 時才會被加載、初始化
    private static class Holder {
        static final Singleton4 INSTANCE = new Singleton4();
    }

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

原理:利用類加載機制保證線程安全,利用內部類延遲加載特性實現懶加載。無鎖、高性能、代碼簡潔。

5. 雙重檢查鎖 DCL(高性能懶加載標準寫法)

public class Singleton5 {
    // volatile 禁止指令重排,保證可見性
    private volatile static Singleton5 instance;
    private Singleton5() {}

    public static Singleton5 getInstance() {
        if (instance == null) {           // 第一次檢查:無鎖,性能高
            synchronized (Singleton5.class) {  // 類級別鎖
                if (instance == null) {   // 第二次檢查:防止重複創建
                    instance = new Singleton5();
                }
            }
        }
        return instance;
    }
}

三、DCL 核心難點:為什麼需要 volatile?

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

  1. 分配內存空間
  2. 初始化對象
  3. 將引用指向內存地址

若發生 2↔3 重排,線程 A 執行完步驟 3 但未完成步驟 2,線程 B 進入第一次檢查發現 instance != null,直接返回半初始化對象,導致崩潰。

volatile 禁止指令重排 + 保證可見性,強制執行順序 1→2→3,解決該問題。

四、避坑指南

  • ❌ 不要用「同步代碼塊鎖 this」:構造方法未結束,this 尚未完全構造
  • ❌ 不要省略第二次 if (instance == null):否則退化為同步方法
  • ✅ JDK 1.5+ 才修復了 volatile 語義,舊版本 DCL 失效
  • ✅ 反射可破壞單例,需在構造方法中檢查 if (instance != null) throw Exception
  • ✅ 序列化需實現 readResolve() 返回現有實例

五、枚舉單例(Effective Java 作者推薦)

public enum SingletonEnum {
    INSTANCE;
    public void doSomething() { ... }
}
// 使用:SingletonEnum.INSTANCE.doSomething();

優勢:天然防反射、防序列化破壞、代碼極簡、JVM 保證線程安全。


工程實踐建議:優先靜態內部類,極致性能場景用 DCL,簡單場景用枚舉。理解 DCL 中 volatile 的作用,是掌握 Java 內存模型、指令重排、Happens-Before 規則的絕佳切入點。

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