單例模式確保類在全局僅有一個實例,並提供訪問點。Java 中實現單例有多種寫法,各有優劣。本文系統梳理 5 種主流實現,重點分析線程安全、延遲加載、序列化安全、反射攻擊防禦,推薦生產環境最佳選擇。
public class HungrySingleton {
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton() {}
public static HungrySingleton getInstance() { return INSTANCE; }
}
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static synchronized LazySingleton getInstance() {
if (instance == null) instance = new LazySingleton();
return instance;
}
}
synchronized 鎖定整個方法,高併發下效能極差(實際只需首次加鎖)。public class DclSingleton {
private static volatile DclSingleton instance;
private DclSingleton() {}
public static DclSingleton getInstance() {
if (instance == null) {
synchronized (DclSingleton.class) {
if (instance == null) instance = new DclSingleton();
}
}
return instance;
}
}
volatile 禁止指令重排,保證構造函數執行完成前不暴露引用。public class HolderSingleton {
private HolderSingleton() {}
private static class Holder {
static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() { return Holder.INSTANCE; }
}
getInstance() 觸發內部類加載並初始化 INSTANCE,JVM 保證線程安全。public enum EnumSingleton {
INSTANCE;
public void doSomething() { /* ... */ }
}
| 攻擊方式 | 餓漢/靜態內部類 | 雙重檢查鎖 | 枚舉 |
|---|---|---|---|
| 反射調用私有構造器 | 可破壞(需加入構造器檢查) | 可破壞 | 免疫 (JVM 禁止) |
| 序列化/反序列化 | 可破壞(需實現 readResolve) | 可破壞 | 免疫 |
| 類加載器隔離 | 可產生多實例 | 可產生多實例 | 可產生多實例 |
private Singleton() {
if (Holder.INSTANCE != null) { // 或針對各自實例變量
throw new IllegalStateException("單例已存在,禁止反射創建");
}
}
// 非枚舉類需實現
private Object readResolve() { return INSTANCE; }
單例模式看似簡單,實則蘊含 Java 記憶體模型、類加載機制、反射/序列化機制等核心知識。靜態內部類 平衡了延遲加載、線程安全、代碼簡潔,是最通用的推薦寫法;枚舉 則是安全性最高的終極形態。理解各實現的底層原理與適用邊界,才能在架構設計中做出正確選擇。