單例模式確保類在全局僅有一個實例,並提供全局訪問點。它廣泛應用於線程池、緩存、日誌、配置管理、數據庫連接池等資源控制場景。本文系統梳理七種經典寫法,深度分析線程安全、性能、序列化/反射攻擊防禦,給出生產環境最佳選擇。
newpublic class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; }
}
public class Singleton {
private static final Singleton INSTANCE;
static {
try { INSTANCE = new Singleton(); }
catch (Exception e) { throw new RuntimeException(e); }
}
// ...
}
public static synchronized Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查
synchronized (Singleton.class) { // 加鎖
if (instance == null) { // 第二次檢查
instance = new Singleton(); // 關鍵:需 volatile
}
}
}
return instance;
}
// 必須加 volatile 防止指令重排序!
private static volatile Singleton instance;
為什麼需要 volatile? new Singleton() 非原子操作:分配內存 → 初始化對象 → 指向引用。重排序可能導致其他線程看到「已分配但未初始化」的對象。
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() { return Holder.INSTANCE; }
}
setAccessible(true) 破壞)public enum Singleton {
INSTANCE;
public void doSomething() { /* ... */ }
}
現代框架由 IOC 容器管理生命週期,本質是「註冊式單例」。
| 攻擊方式 | 破壞原理 | 防禦方案 |
|---|---|---|
| 反射 | Constructor.setAccessible(true) 調用私有構造器 | 枚舉單例 / 構造器中拋異常(如 if (INSTANCE != null) throw...) |
| 序列化/反序列化 | readObject() 默認創建新實例 | 實現 readResolve() 返回現有實例 / 使用枚舉 |
| 場景 | 推薦寫法 | 理由 |
|---|---|---|
| 通用業務單例 | 靜態內部類 | 延遲加載+線程安全+簡潔高性能 |
| 安全敏感/防破壞 | 枚舉單例 | JVM 級防反射、防序列化破壞 |
| 需配置參數初始化 | DCL + volatile | 可在 getInstance 內處理複雜初始化邏輯 |
| Spring 管理 Bean | @Scope("singleton") | 容器統一生命週期,支援 AOP、依賴注入 |
單例模式看似簡單,實則蘊含「類加載機制」、「內存模型」、「反射/序列化機制」等核心知識。靜態內部類是通用首選,枚舉是安全首選。理解 volatile 在 DCL 中的作用,更能加深對 JMM happens-before 原則的掌握。