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

Java 單例模式與多線程安全完整指南

     編輯:Java 相關知識

單例模式核心要素

  • 構造器私有化,防止外部實例化。
  • 提供靜態工廠方法獲取唯一實例。
  • 確保多線程環境下實例唯一性(線程安全)。
  • 防止反射、序列化破壞單例。

七種實現方式對比

模式線程安全延遲加載性能防反射/序列化推薦指數
餓漢式 (靜態常量)✅ 類加載時初始化❌極高需額外處理⭐⭐⭐
餓漢式 (靜態代碼塊)✅❌極高需額外處理⭐⭐⭐
懶漢式 (同步方法)✅✅低 (鎖粗)需額外處理⭐
懶漢式 (同步代碼塊)❌ 無效✅低需額外處理❌
雙重檢查鎖 (DCL)✅ (需 volatile)✅高需額外處理⭐⭐⭐⭐
靜態內部類✅ 類加載機制保證✅高需額外處理⭐⭐⭐⭐⭐
枚舉單例✅ JVM 保證✅ (調用時)高✅ 天然防反射/序列化⭐⭐⭐⭐⭐

代碼示例

1. 餓漢式 (靜態常量)

public class HungrySingleton {
    private static final HungrySingleton INSTANCE = new HungrySingleton();
    private HungrySingleton() {}
    public static HungrySingleton getInstance() { return INSTANCE; }
}

2. 懶漢式 + DCL (推薦 JDK 5+)

public class DclSingleton {
    private static volatile DclSingleton instance;
    private DclSingleton() {}
    public static DclSingleton getInstance() {
        if (instance == null) {                    // 第一次檢查,無鎖
            synchronized (DclSingleton.class) {    // 加鎖
                if (instance == null) {            // 第二次檢查
                    instance = new DclSingleton(); // 實例化
                }
            }
        }
        return instance;
    }
}

volatile 禁止指令重排序,保證其他線程看到完全初始化的對象。

3. 靜態內部類 (推薦通用)

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

利用類加載器初始化靜態內部類時的線程安全特性,兼具延遲加載與高性能。

4. 枚舉單例 (最強健壯)

public enum EnumSingleton {
    INSTANCE;
    private Connection conn;
    EnumSingleton() {
        // 初始化資源
    }
    public Connection getConnection() { return conn; }
}

JVM 保證枚舉常量只實例化一次,天然防反射攻擊(Constructor.newInstance 拋異常)、防序列化破壞(readResolve 不需要)。

序列化/反序列化破壞單例與修復

實現 Serializable 後,反序列化會創建新對象。修復:

protected Object readResolve() {
    return INSTANCE; // 返回現有實例
}

枚舉單例無需此方法。

多線程壓測驗證

public class SingletonTest {
    public static void main(String[] args) throws InterruptedException {
        int threads = 1000;
        CountDownLatch latch = new CountDownLatch(threads);
        Set<Object> instances = Collections.synchronizedSet(new HashSet<>());

        for (int i = 0; i < threads; i++) {
            new Thread(() -> {
                instances.add(EnumSingleton.INSTANCE); // 替換測試目標
                latch.countDown();
            }).start();
        }
        latch.await();
        System.out.println("實例數量: " + instances.size()); // 應為 1
    }
}

選型建議

  • 無特殊需求、追求簡潔健壯 → 枚舉單例。
  • 需要延遲加載、不依賴枚舉特性 → 靜態內部類。
  • 需手動控制初始化時機、或需懶加載且無枚舉約束 → DCL (volatile)。
  • 類加載即需實例、資源佔用小 → 餓漢式。

避坑指南

  • 懶漢式同步代碼塊鎖 this 或非類對象 → 無效,必須鎖 Class 對象。
  • DCL 缺少 volatile → 指令重排導致半初始化對象逃逸。
  • 反射調用私有構造器 → 拋出異常或返回現有實例(枚舉不可破壞)。
  • 單例持有狀態 → 多線程併發修改需額外同步或使用 ThreadLocal。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved