單例模式是 Java 開發中最基礎也最常用的設計模式之一,核心在於確保一個類在整個應用程式生命週期中僅存在一個實例,並提供全域存取點。雖然目標單一,但根據執行緒安全性、延遲載入需求以及效能考量,衍生出多種實現策略。本文將系統整理四種主流寫法,並從原理、優缺點及適用場景三個維度進行深度剖析。
這是目前公認最優雅、最標準的單例實現方式,完美結合了「延遲載入」與「執行緒安全」,且無需額外同步開銷。
public class Singleton {
// 私有建構子,防止外部實例化
private Singleton() {}
// 靜態內部類,只有在首次呼叫 getResource() 時才會被類載入器初始化
private static class SingletonHolder {
public static final Singleton INSTANCE = new Singleton();
}
public static Singleton getResource() {
return SingletonHolder.INSTANCE;
}
}
核心優勢:利用 JVM 類載入機制天然保證執行緒安全;外部類載入時不會初始化內部類,真正實現 Lazy Loading;呼叫 getResource() 時無同步鎖,效能極高。
利用 Java 枚舉類型本身的特性(序列化安全、防反射攻擊、天然執行緒安全),以最少程式碼實現最健壯的單例。
public enum Singleton {
INSTANCE;
public void doSomething() {
// 業務邏輯
}
}
核心優勢:語法簡潔至極致;JVM 保證只會實例化一次;免疫反射破壞(Constructor.newInstance() 會拋出異常);自動處理序列化/反序列化場景,無需實作 readResolve()。缺點:不支援延遲載入(類載入即實例化),且無法繼承其他類。
在類載入階段就完成實例化,由 ClassLoader 機制保證執行緒安全。
public class Singleton {
public static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public void method() { /* ... */ }
}
適用場景:物件建構開銷極小、且確定會被使用的情況。缺點:無法延遲載入;若建構過程拋出異常會導致類載入失敗(ExceptionInInitializerError)。
在 JDK 1.5+ 確立 volatile 語意後,成為高效能 Lazy Loading 的標準手寫模板。
public class Singleton {
// volatile 禁止指令重排序,確保其他執行緒看到完全初始化的物件
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查:無鎖,效能最優
synchronized (Singleton.class) { // 同步塊:僅首次建立時爭搶
if (instance == null) { // 第二次檢查:防止重複建立
instance = new Singleton();
}
}
}
return instance;
}
}
關鍵點:volatile 關鍵字不可省略,它解決了「物件參考賦值」與「建構子執行」的指令重排序問題,否則可能返回半初始化物件導致 NPE。
| 實現方式 | 執行緒安全 | 延遲載入 | 防反射 | 序列化安全 | 推薦指數 |
|---|---|---|---|---|---|
| 靜態內部類 | ✅ | ✅ | ❌ | 需自行實作 readResolve | ⭐⭐⭐⭐⭐ |
| 枚舉 | ✅ | ❌ | ✅ | ✅ | ⭐⭐⭐⭐⭐ |
| 餓漢式 | ✅ | ❌ | ❌ | 需自行實作 | ⭐⭐⭐ |
| 雙重檢查鎖 | ✅ | ✅ | ❌ | 需自行實作 | ⭐⭐⭐⭐ |
最佳實踐:一般業務首選靜態內部類;對安全性要求極高(防反射、序列化)場景首選枚舉;需手動控制初始化時機或維護舊專案時使用雙重檢查鎖。