單例模式確保某個類在整個應用程式生命週期中只存在一個實例,並提供全域存取點。適用於資料庫連線池、配置管理器、日誌記錄器等「全域唯一」的場景。
public class Singleton {
// 類載入時即建立實例,天然線程安全
private static final Singleton INSTANCE = new Singleton();
private Singleton() { } // 私有建構子,防止外部 new
public static Singleton getInstance() {
return INSTANCE;
}
}
特點:
public class Singleton {
private static volatile Singleton instance; // volatile 禁止指令重排
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查,避免不必要同步
synchronized (Singleton.class) {
if (instance == null) { // 第二次檢查,確保單一建構
instance = new Singleton();
}
}
}
return instance;
}
}
關鍵點:volatile 確保 instance = new Singleton() 的「分配記憶體 → 初始化物件 → 指向參考」三步驟不被重排,避免其他執行緒拿到半初始化的物件。
public class Singleton {
private Singleton() { }
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
優勢:利用類載入機制天然保證線程安全,且延遲到首次呼叫 getInstance() 才初始化,碼簡潔、效能最佳。
public enum Singleton {
INSTANCE;
public void doSomething() { ... }
}
《Effective Java》作者 Joshua Bloch 強力推薦。枚舉序列化機制由 JVM 保證單一實例,且無法透過反射呼叫私有建構子。
| 問題 | 原因 | 解決 |
|---|---|---|
| 反射破壞 | Constructor.setAccessible(true) 呼叫私有建構子 | 建構子中拋出異常,或改用枚舉 |
| 反序列化產生新實例 | 預設反序列化不經建構子 | 實作 readResolve() 回傳既有實例,或用枚舉 |
| 多類載入器環境下出現多實例 | 不同 ClassLoader 載入同一類視為不同類 | 指定統一父類載入器,或採用模組化系統 |
單例模式看似簡單,實則暗藏線程安全、序列化、反射等細節。生產環境首推靜態內部類或枚舉,兼顧延遲初始化、線程安全與防破壞,是經得起時間考驗的最佳實踐。