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

Java 單例模式深度解析:餓漢式、懶漢式與靜態內部類寫法演進

     編輯:Java 相關知識

單例模式核心要求

  • 全域唯一實例
  • 私有建構子防止外部實例化
  • 提供全域存取點 getInstance()
  • 執行緒安全
  • 支援延遲載入(可選)

寫法一:餓漢式——最簡單、最可靠

public class HungrySingleton {
    // 類載入時即建立實例,JVM 保證執行緒安全
    private static final HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton() {} // 私有建構子

    public static HungrySingleton getInstance() {
        return INSTANCE;
    }
}

優點:無鎖、效能最高、實現最簡單、JVM 類載入機制天然保證安全。
缺點:無延遲載入,若實例建構耗時/資源佔用大且未必使用,會造成浪費。

寫法二:懶漢式(雙重檢查鎖)——經典但易寫錯

public class LazySingletonDCL {
    private static volatile LazySingletonDCL instance; // volatile 關鍵!

    private LazySingletonDCL() {}

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

關鍵點:volatile 禁止指令重排,防止半初始化物件被其他執行緒見到。
缺點:程式碼較繁瑣,效能較餓漢式稍差(首次需同步)。

寫法三:靜態內部類——完美平衡(推薦)

public class LazySingletonHolder {
    private LazySingletonHolder() {}

    // 只有呼叫 getInstance() 時才會載入 SingletonHolder 類
    private static class SingletonHolder {
        static final LazySingletonHolder INSTANCE = new LazySingletonHolder();
    }

    public static LazySingletonHolder getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

為何執行緒安全且延遲載入?

  1. 外部類載入時,內部類 不會 被載入(JVM 規範)
  2. 首次呼叫 getInstance() 觸發 SingletonHolder 初始化
  3. 類初始化階段由 JVM 鎖保護,天然執行緒安全
  4. 無需 volatile、無需同步塊、零額外開銷

載入順序驗證實驗

public class SingletonKerriganF {
    private static String arg = null;
    
    private static class SingletonHolder {
        static final SingletonKerriganF INSTANCE = new SingletonKerriganF();
    }
    
    public static SingletonKerriganF getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    private SingletonKerriganF() {
        System.out.println("Kerrigan get arg: " + getArg()); // 印出 null
        System.out.println("Kerrigan created!");
    }
    
    public static String getArg() { return arg; }
    public static void setArg(String arg) { SingletonKerriganF.arg = arg; }
    
    public static void main(String[] args) {
        System.out.println("外部類載入完成,內部類尚未載入");
        setArg("config from file"); // 設定配置
        getInstance(); // 此時才觸發內部類載入與實例建構
    }
}

輸出結果:

外部類載入完成,內部類尚未載入
Kerrigan get arg: config from file  // 配置已生效!
Kerrigan created!

證明:內部類載入發生在 getInstance() 呼叫時,而非外部類載入時,完美支援「先配置、後建構」場景。

其他寫法補充

枚舉單例(Effective Java 推薦)

public enum EnumSingleton {
    INSTANCE;
    public void doSomething() { /* 業務邏輯 */ }
}

優點:防序列化破壞、防反射攻擊、語法最簡潔。缺點:無延遲載入、無法繼承。

選型決策表

場景推薦寫法理由
一般業務單例、資源佔用小餓漢式最簡單、效能最好、零 Bug 風險
建構耗時大、資源佔用高、可能不使用靜態內部類延遲載入 + 執行緒安全 + 無鎖讀取
需防序列化/反射破壞、或偏好極簡語法枚舉單例語言層面保證單一性
需懶載入且 JDK < 1.5(無 volatile 語意)同步方法舊環境相容,效能差

總結

現代 Java 開發中,餓漢式與靜態內部類覆蓋 95%+ 場景。理解 JVM 類載入機制與 volatile 語意,是寫出正確高效單例的關鍵。切記:雙重檢查鎖若漏掉 volatile 即為錯誤寫法。

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