確保類在全局範圍內僅有一個實例,並提供統一訪問入口。關鍵約束:構造器私有化、靜態工廠方法、防止反射/序列化破壞(本文未涉及)。
| 維度 | 餓漢式 | 懶漢式 |
|---|---|---|
| 初始化時機 | 類加載階段(靜態初始化) | 首次調用 getInstance() |
| 資源佔用 | 可能造成不必要的早期佔用 | 按需分配,節省資源 |
| 線程安全 | 天然安全(JVM 保證類初始化原子性) | 需額外同步機制 |
| 適用場景 | 實例創建輕量、必然被使用 | 創建開銷大、可能不被使用 |
public class Singleton1 {
private static final Singleton1 INSTANCE = new Singleton1();
private Singleton1() {}
public static Singleton1 getInstance() { return INSTANCE; }
}
public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {}
public static Singleton2 getInstance() {
if (instance == null) instance = new Singleton2();
return instance;
}
}
併發缺陷:多線程同時通過 instance == null 判斷,導致多次實例化。
public static synchronized Singleton3 getInstance() {
if (instance == null) instance = new Singleton3();
return instance;
}
每次調用均同步,嚴重影響高併發性能。
public class Singleton4 {
private static volatile Singleton4 instance;
private Singleton4() {}
public static Singleton4 getInstance() {
if (instance == null) { // 第一次檢查(無鎖)
synchronized (Singleton4.class) { // 加鎖
if (instance == null) { // 第二次檢查(鎖內)
instance = new Singleton4(); // 實例化
}
}
}
return instance;
}
}
instance = new Singleton4() 非原子操作,實際分三步:分配內存、初始化對象、引用賦值。JVM 指令重排序可能導致「引用賦值」先於「初始化完成」,其他線程看到非 null 引用卻訪問到半初始化對象。volatile 禁止指令重排序,保證「寫入內存可見性」與「有序性」。
JDK 1.4 及之前:volatile 無法禁止重排序,DCL 失效。
JDK 1.5+:JSR-133 強化 volatile 語義,DCL 完全可用。
public class Singleton5 {
private Singleton5() {}
private static class Holder {
static final Singleton5 INSTANCE = new Singleton5();
}
public static Singleton5 getInstance() { return Holder.INSTANCE; }
}
利用類加載機制天然線程安全、延遲初始化、代碼簡潔,無 volatile 複雜度。
public enum Singleton6 {
INSTANCE;
public void doSomething() {}
}
天然防序列化破壞、防反射攻擊、線程安全,但無法延遲加載(枚舉類加載即實例化)。
| 場景 | 推薦方案 | 理由 |
|---|---|---|
| 通用業務、輕量對象 | 餓漢式 / 靜態內部類 | 簡單可靠,無併發風險 |
| 重資源對象、可能不使用 | 靜態內部類 / DCL | 延遲加載 + 高性能 |
| 需防序列化/反射破壞 | 枚舉單例 | 語言級保證 |
| 框架級組件、需擴展性 | Spring 容器管理 @Scope("singleton") | 統一生命週期、AOP 支持 |
單例模式看似簡單,實則蘊含 Java 內存模型、類加載機制、併發控制等核心知識。現代開發中,靜態內部類與枚舉是最佳平衡點;DCL 適合需精確控制初始化邏輯的場景。切記:業務代碼優先考慮依賴注入框架管理單例,而非手寫模式。