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

Java 單例模式的多種實現方式與線程安全分析

     編輯:Java 綜合教學

Java 單例模式的多種實現方式與線程安全分析

單例模式是最經典的設計模式之一,目標是確保一個類在全局範圍內只有一個實例,並提供一個全局訪問點。雖然教科書上常見的寫法看似簡單,但在多線程環境下卻暗藏隱患。本文將從最基礎的實現開始,逐步演進到線程安全、高效能的最佳實踐。

1. 基礎版:懶漢模式(非線程安全)

最直觀的寫法是在首次調用時創建實例:

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

致命缺陷: 當多線程並發調用 getInstance() 時,多個線程可能同時通過 instance == null 判斷,導致創建多個實例,違背單例初衷。

2. 同步鎖優化:解決線程安全但效率低

2.1 方法級同步

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

雖然解決了線程安全,但每次調用都需獲取鎖,效能極差,不推薦生產環境使用。

2.2 雙重檢查鎖

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

注意: 必須將 instance 宣告為 volatile,防止指令重排序導致半初始化對象被其他線程看到。JDK 1.5+ 後此寫法才真正安全。

3. 靜態內部類:推薦的懶加載寫法

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

優點: 利用類加載機制天然保證線程安全,無鎖、延遲初始化、程式碼簡潔,是業界公認的最佳實踐之一。

4. 枚舉單例:防反射與反序列化攻擊

public enum Singleton {
    INSTANCE;
    public void doSomething() {}
}

優點: 不僅線程安全,還能天然防止反射攻擊(構造器私有)和反序列化產生新實例,《Effective Java》強力推薦。

5. 測試驗證

以下測試代碼模擬多線程環境驗證單例性:

public class Test {
    public static void main(String[] args) {
        Runnable task = () -> {
            Singleton s = Singleton.getInstance();
            System.out.println(Thread.currentThread().getId() + ": " + s.hashCode());
        };
        new Thread(task).start();
        new Thread(task).start();
    }
}

輸出顯示兩個線程獲取到的實例 hashCode 相同,證明為同一對象。

總結建議

  • 一般場景:優先使用靜態內部類或枚舉。
  • 需防反射/反序列化破壞:必選枚舉。
  • 避免使用雙重檢查鎖(除非深諳 JMM 且加 volatile)。
  • 餓漢模式(靜態最終字段直接初始化)雖簡單但無延遲加載優勢,適用於初始化開銷小的場景。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved