單例模式是最基礎也最常被考察的創建型模式。本文系統性解析單例模式的三大動機、五種主流實作、執行緒安全本質、序列化/反射防禦,並提供生產環境選型決策表。
public final class SingletonEager {
private static final SingletonEager INSTANCE = new SingletonEager();
private SingletonEager() {}
public static SingletonEager getInstance() { return INSTANCE; }
}
public final class SingletonHolder {
private SingletonHolder() {}
private static class Holder {
static final SingletonHolder INSTANCE = new SingletonHolder();
}
public static SingletonHolder getInstance() { return Holder.INSTANCE; }
// 序列化防禦
protected Object readResolve() { return getInstance(); }
}
public final class SingletonDCL {
private volatile static SingletonDCL instance;
private SingletonDCL() {}
public static SingletonDCL getInstance() {
if (instance == null) {
synchronized (SingletonDCL.class) {
if (instance == null) instance = new SingletonDCL();
}
}
return instance;
}
}
public enum SingletonEnum {
INSTANCE;
public void doBusiness() { /* ... */ }
}
public class SerializableSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private static final SerializableSingleton INSTANCE = new SerializableSingleton();
private SerializableSingleton() {}
public static SerializableSingleton getInstance() { return INSTANCE; }
protected Object readResolve() { return INSTANCE; } // 關鍵:反序列化回傳單例
}
| 維度 | 餓漢式 | 靜態內部類 | DCL | Enum |
|---|---|---|---|---|
| 執行緒安全 | ✅ (JVM 保證) | ✅ (JVM 保證) | ✅ (volatile) | ✅ (語言級) |
| 延遲載入 | ❌ | ✅ | ✅ | ❌ |
| 效能 (獲取) | 極快 (直接讀取) | 極快 (直接讀取) | 快 (一次 volatile 讀取) | 極快 |
| 防反射攻擊 | ❌ | ❌ | ❌ | ✅ (語言保證) |
| 防序列化破壞 | 需 readResolve | 需 readResolve | 需 readResolve | ✅ (自動) |
| 可繼承/實作介面 | ✅ | ✅ | ✅ | ❌ (不可繼承) |
| 程式碼複雜度 | 低 | 低 | 中 | 極低 |
instance = new SingletonDCL() 非原子,分三步:
無 volatile 時,JVM/CPU 可能重排為 1→3→2,導致其他執行緒看到「非 null 但未初始化完成」的物件。volatile 禁止此重排。
JVM 規範:類別首次主動使用時才初始化。外部類載入不觸發內部類初始化,僅在呼叫 Holder.INSTANCE 時觸發,且由 JVM 鎖定初始化過程,天然執行緒安全。
java.lang.Enum 的 Constructor 為 private 且 JVM 內部特殊處理,Constructor.newInstance() 會拋出 IllegalArgumentException。
需要延遲載入?
├── 否 → 需防反射/序列化破壞? → 是 → **Enum** (首選)
│ └── 否 → **餓漢式** (最簡單)
└── 是 → 需繼承類別/實作介面? → 是 → **靜態內部類** (推薦)
└── 否 → 極致效能敏感? → **DCL** (罕見)
└── 否 → **靜態內部類** (推薦)
Serializable 必須加 readResolve(),否則每次反序列化產生新實例ExceptionInInitializerError),後續無法重試;DCL/Enum 可在 getInstance() 中捕獲重試一句话總結:
✅ 90% 場景用 Enum (最安全、最簡潔)
✅ 需延遲載入/繼承 → 靜態內部類
⚠️ 極罕見效能極限 → DCL + volatile
❌ 手寫同步方法/雙重檢查無 volatile → 錯誤範本