單例模式確保類在全局僅有一個實例,並提供訪問入口。本文系統對比五種主流實現:餓漢式、懶漢式同步、雙重檢查鎖 (DCL)、靜態內部類、枚舉,分析線程安全性、延遲加載、序列化/反射破壞防禦與性能,給出生產環境選型建議。
public class SingletonEager {
private static final SingletonEager INSTANCE = new SingletonEager();
private SingletonEager() {}
public static SingletonEager getInstance() { return INSTANCE; }
}
| 優點 | 缺點 |
|---|---|
| 無鎖、效能最高、JVM 保證線程安全 | 類加載即創建,無延遲加載;若實例從未使用浪費記憶體 |
public class SingletonSync {
private static SingletonSync instance;
private SingletonSync() {}
public static synchronized SingletonSync getInstance() {
if (instance == null) instance = new SingletonSync();
return instance;
}
}
| 優點 | 缺點 |
|---|---|
| 延遲加載、線程安全 | 每次調用都同步,性能差 (僅首次需鎖) |
public class SingletonDCL {
private static volatile SingletonDCL instance; // 關鍵:volatile
private SingletonDCL() {}
public static SingletonDCL getInstance() {
if (instance == null) { // 第一次檢查 (無鎖)
synchronized (SingletonDCL.class) { // 鎖類對象
if (instance == null) { // 第二次檢查
instance = new SingletonDCL(); // 實例化
}
}
}
return instance;
}
}
關鍵點:
volatile 禁止指令重排序,確保 `instance = new SingletonDCL()` 的三步(分配記憶體、初始化對象、賦值引用)不被重排,避免其他線程看到「半初始化」對象。
public class SingletonHolder {
private SingletonHolder() {}
private static class Holder {
static final SingletonHolder INSTANCE = new SingletonHolder();
}
public static SingletonHolder getInstance() {
return Holder.INSTANCE;
}
}
原理:JVM 類加載機制保證 `Holder` 類首次被主動使用時(調用 `getInstance()` 觸發)初始化,過程線程安全、無需同步。兼具延遲加載與高性能。
public enum SingletonEnum {
INSTANCE;
public void doSomething() { ... }
}
// 使用:SingletonEnum.INSTANCE.doSomething();
| 特性 | 枚舉 | 其他寫法 |
|---|---|---|
| 線程安全 | ✅ JVM 保證 | 視實現 |
| 防反射攻擊 | ✅ 構造器私有且 JVM 特殊處理 | ❌ 可用反射強行調用構造器 |
| 防序列化破壞 | ✅ 自動處理 | ❌ 需實現 readResolve() |
| 延遲加載 | ✅ 首次使用時初始化 | 視實現 |
| 代碼簡潔度 | ✅ 極簡 | 一般 |
// 破壞普通單例
Constructor> ctor = SingletonDCL.class.getDeclaredConstructor();
ctor.setAccessible(true);
SingletonDCL s1 = SingletonDCL.getInstance();
SingletonDCL s2 = ctor.newInstance(); // 第二個實例!
System.out.println(s1 == s2); // false
// 防禦:在構造器中檢查
private SingletonDCL() {
if (instance != null) {
throw new IllegalStateException("單例已存在,禁止反射創建");
}
}
枚舉天然免疫:JVM 禁止反射調用枚舉構造器,拋 `IllegalArgumentException`。
// 普通單例需實現 readResolve
private Object readResolve() {
return INSTANCE; // 反序列化時返回同一實例
}
枚舉序列化由 JVM 特殊處理,無需額外代碼。
| 維度 | 餓漢式 | 同步懶漢 | DCL | 靜態內部類 | 枚舉 |
|---|---|---|---|---|---|
| 線程安全 | ✅ | ✅ | ✅ (Java 5+) | ✅ | ✅ |
| 延遲加載 | ❌ | ✅ | ✅ | ✅ | ✅ |
| 性能 (獲取實例) | 極高 | 低 | 高 | 極高 | 極高 |
| 防反射 | ❌ | ❌ | ❌ (可加檢查) | ❌ | ✅ |
| 防序列化破壞 | ❌ | ❌ | ❌ (可加 readResolve) | ❌ | ✅ |
| 代碼複雜度 | 低 | 低 | 中 | 低 | 極低 |
| 推薦場景 | 輕量、必用 | 教學演示 | 舊版本兼容 | **通用首選** | **安全敏感、新專案** |