單例模式確保類在全局僅有一個實例,並提供全局訪問點。本文梳理五種經典實現,從線程不安全到高性能線程安全,層層遞進。
public class Singleton1 {
private static Singleton1 instance;
private Singleton1() {}
public static Singleton1 getInstance() {
if (instance == null) {
instance = new Singleton1(); // 多線程下可能創建多個實例
}
return instance;
}
}
❌ 多線程併發時會創建多個實例。
public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {}
public static synchronized Singleton2 getInstance() { // 鎖住整個方法
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
✅ 線程安全,❌ 每次調用都同步,性能差,實際只需首次創建時同步。
public class Singleton3 {
private static final Singleton3 instance = new Singleton3();
private Singleton3() {}
public static Singleton3 getInstance() { return instance; }
}
✅ 簡單、線程安全(類加載機制保證),❌ 無懶加載,未使用也會創建,浪費資源。
public class Singleton4 {
private Singleton4() {}
public static Singleton4 getInstance() {
return Holder.INSTANCE;
}
private static class Holder {
private static final Singleton4 INSTANCE = new Singleton4();
}
}
✅ 推薦寫法:
public class Singleton5 {
private volatile static Singleton5 instance; // 關鍵:volatile 防指令重排
private Singleton5() {}
public static Singleton5 getInstance() {
if (instance == null) { // 第一次檢查,無鎖
synchronized (Singleton5.class) { // 同步塊
if (instance == null) { // 第二次檢查
instance = new Singleton5(); // 創建實例
}
}
}
return instance;
}
}
instance = new Singleton5() 非原子操作,實際分三步:
JVM 可能為優化進行指令重排(1→3→2),導致其他線程看到 instance != null 但對象未初始化完成。volatile 禁止重排,保證可見性與有序性。
| 寫法 | 懶加載 | 線程安全 | 性能 | 推薦度 |
|---|---|---|---|---|
| 懶漢式 | ✅ | ❌ | 高 | ❌ |
| 同步懶漢 | ✅ | ✅ | 低 | ❌ |
| 餓漢式 | ❌ | ✅ | 高 | ⚠️ 簡單場景 |
| 靜態內部類 | ✅ | ✅ | 高 | ✅ 首選 |
| DCL | ✅ | ✅ | 高 | ✅ 兼容舊 JDK |
public enum Singleton {
INSTANCE;
public void doSomething() { ... }
}
✅ Effective Java 首選:
| 手段 | 防禦 |
|---|---|
| 反射調用私有構造器 | 構造器中檢查 if (instance != null) throw ...,或用枚舉 |
| 反序列化 | 實現 readResolve() 返回現有實例,或用枚舉 |
| 克隆 | 不實現 Cloneable,或拋異常 |