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

Java 多線程環境下線程安全單例模式的七種實現與對比

     編輯:Java 相關知識

前言

單例模式確保類在全局僅有一個實例,並提供全局訪問點。在多線程環境下,必須保證實例化過程的線程安全。本文總結七種線程安全單例實現,從餓漢式到枚舉,分析優劣與適用場景。

1. 餓漢式(靜態常量/靜態代碼塊)

類加載時即創建實例,天然線程安全(JVM 保證類初始化原子性)。

// 方式 A:靜態常量
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() { return INSTANCE; }
}

// 方式 B:靜態代碼塊(可添加異常處理邏輯)
public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    static {
        try { instance = new Singleton(); }
        catch (Exception e) { throw new RuntimeException("初始化失敗", e); }
    }
    public static Singleton getInstance() { return instance; }
}

優點:簡單、無鎖、性能最高。
缺點:類加載即創建,無延遲加載能力;若實例從未使用則浪費內存。

2. 懶漢式 + synchronized 方法鎖

public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public synchronized static Singleton getInstance() {
        if (instance == null) instance = new Singleton();
        return instance;
    }
}

缺點:鎖粒度過大,每次調用均同步,性能極差。

3. 懶漢式 + 同步代碼塊(類鎖)

public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            instance = new Singleton();
        }
    }
    return instance;
}

缺點:同步塊內仍包含非關鍵代碼,性能未本質提升。

4. 懶漢式 + 雙重檢查鎖(DCL)

public static Singleton getInstance() {
    if (instance == null) { // 第一次檢查
        synchronized (Singleton.class) {
            if (instance == null) { // 第二次檢查
                instance = new Singleton();
            }
        }
    }
    return instance;
}

關鍵:instance 必須聲明為 volatile,防止指令重排導致半初始化對象被其他線程看到。

5. 靜態內部類(Holder 模式)

public class Singleton {
    private Singleton() {}
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() { return Holder.INSTANCE; }
}

原理:利用類加載機制,外部類加載時不初始化內部類,首次調用 getInstance() 觸發內部類加載,JVM 保證線程安全。
優點:延遲加載、無鎖、代碼簡潔,推薦生產環境使用。

6. 枚舉單例

public enum Singleton {
    INSTANCE;
    public void doSomething() { System.out.println("執行業務邏輯"); }
}

優點:語法簡潔、天然防反射攻擊、防序列化破壞、JVM 保證線程安全。缺點:無法延遲加載(枚舉類加載即實例化)、無法繼承其他類。

7. 序列化/反序列化安全單例

若單例實現 Serializable,反序列化會創建新實例,需添加 readResolve() 方法:

public class Singleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static class Holder { static final Singleton INSTANCE = new Singleton(); }
    private Singleton() {}
    public static Singleton getInstance() { return Holder.INSTANCE; }
    // 反序列化時返回唯一實例
    protected Object readResolve() { return Holder.INSTANCE; }
}

對比總結表

實現方式線程安全延遲加載性能防反射防序列化破壞推薦指數
餓漢式(常量/靜態塊)✓✗極高✗✗★★★☆☆
synchronized 方法✓✓低✗✗★☆☆☆☆
同步代碼塊✓✓低✗✗★☆☆☆☆
DCL (volatile)✓✓高✗✗★★★★☆
靜態內部類✓✓高✗✗★★★★★
枚舉✓✗高✓✓★★★★★
內部類+readResolve✓✓高✗✓★★★★☆

最佳實踐建議

  1. 一般業務:優先選擇靜態內部類,兼顧延遲加載與高性能。
  2. 需防反射/序列化破壞:使用枚舉。
  3. 需支持序列化且延遲加載:內部類 + readResolve()。
  4. 避免使用 synchronized 方法/代碼塊版本,性能瓶頸明顯。

總結

單例模式看似簡單,多線程下正確實現需考慮內存可見性、指令重排、反射攻擊、序列化破壞等細節。掌握上述七種寫法的原理與優劣,能根據業務場景做出最優架構決策。

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