確保類別在全域僅有一個實例,並提供全域存取點。核心挑戰:執行緒安全、延遲載入、防止序列化/反射破壞、效能最佳化。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
}
❌ 多執行緒並發會建立多個實例
public static synchronized Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
✅ 安全 ❌ 鎖粒度太大,實例化後每次呼叫都同步
public static Singleton getInstance() {
if (instance == null) { // First check
synchronized (Singleton.class) { // 類別級鎖
if (instance == null) { // Second check
instance = new Singleton(); // 非原子操作!
}
}
}
return instance;
}
指令重排風險:instance = new Singleton() 分三步:①分配記憶體 ②初始化物件 ③指向記憶體。JVM 可能重排為 ①③②,導致其他執行緒見到「非 null 但未初始化完成」的實例。
private static volatile Singleton instance; // 禁止指令重排
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
volatile 確保:①禁止指令重排 ②可見性(寫入對其他執行緒立即可見)
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton(); // 類別載入時初始化
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
✅ 利用類別載入機制天然執行緒安全 ✅ 外部類別載入時不初始化 Holder,達到延遲載入 ✅ 無同步開銷
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; }
}
✅ 簡單安全 ❌ 無延遲載入,未使用也佔記憶體 ❌ 無法依賴外部參數初始化
public enum Singleton {
INSTANCE;
public void doSomething() { /* 業務邏輯 */ }
}
// 使用:Singleton.INSTANCE.doSomething();
✅ JVM 保證單例 ✅ 天然防序列化破壞 ✅ 天然防反射破壞(Constructor 為 private 且枚舉特殊處理)✅ 程式碼最少
// 反序列化會建立新實例
ObjectInputStream ois = new ObjectInputStream(...);
Singleton s2 = (Singleton) ois.readObject();
s2 != Singleton.getInstance(); // true!
防禦:實作 readResolve() 回傳現有實例,或改用枚舉。
Constructor<?> c = Singleton.class.getDeclaredConstructor();
c.setAccessible(true); // 突破 private
Singleton s2 = c.newInstance();
s2 != Singleton.INSTANCE; // true!
防禦:建構子中檢查 if (instance != null) throw Exception,或改用枚舉(JVM 封殺反射建立枚舉實例)。
| 場景 | 推薦寫法 | 理由 |
|---|---|---|
| 一般業務、需延遲載入 | 靜態內部類 | 安全、高效、延遲 |
| 極簡、需防序列化/反射 | 枚舉 | 最強防禦、程式碼最少 |
| 高併發、效能極致敏感 | volatile DCL | 雙重檢查最小化同步 |
| 類別載入即需使用、資源預熱 | 餓漢式 | 最簡單、無延遲需求 |
單例模式看似簡單,實則蘊含執行緒安全、記憶體可見性、指令重排、類別載入機制、序列化、反射等核心 Java 知識。靜態內部類與枚舉是現代 Java 開發的雙首選,前者平衡延遲載入與效能,後者提供終極防禦。理解演進脈絡,才能在面試與架構設計中遊刃有餘。