確保某個類在**全局(JVM/類加載器範圍)僅有一個實例**,並提供全局訪問點。適用於:資源共享(連接池、線程池、緩存)、控制資源併發訪問、配置管理、日誌記錄器等。
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; }
}
✅ 簡單、線程安全(類加載機制保證) ❌ 類加載即佔用內存,若實例重且未必用到則浪費。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) instance = new Singleton(); // 多線程下會創建多個
return instance;
}
}
✅ 延遲加載 ❌ **線程不安全**,高併發下失效。
public class Singleton {
private static volatile Singleton instance; // 關鍵:volatile 禁止指令重排
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查,無鎖讀取
synchronized (Singleton.class) { // 加鎖
if (instance == null) { // 第二次檢查
instance = new Singleton(); // 實例化(非原子操作:分配內存→初始化→賦值引用)
}
}
}
return instance;
}
}
✅ 延遲加載、高併發性能好 ⚠️ **JDK 5 前 volatile 語義缺陷導致 DCL 失效**,現代 JDK 安全。但代碼較繁瑣。
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton(); // 類加載時初始化,JVM 保證線程安全
}
public static Singleton getInstance() { return Holder.INSTANCE; }
}
✅ 結合餓漢線程安全 + 懶漢延遲加載,**最推薦手寫版本**。利用類加載機制:外部類加載時不加載內部類,首次調用 getInstance() 觸發 Holder 初始化。
public enum Singleton {
INSTANCE;
public void doSomething() { /* 業務邏輯 */ }
}
// 使用:Singleton.INSTANCE.doSomething();
✅ **《Effective Java》強力推薦**。枚舉天然單例,JVM 保證:
① 序列化時只序列化枚舉常量名,反序列化由 Enum.valueOf 保證唯一。
② 構造函數私有,反射 Constructor.setAccessible(true) 會拋異常。
③ 類加載機制天然線程安全。
框架層面保證單例,非代碼層面實現,此處不展開。
Constructor<Singleton> c = Singleton.class.getDeclaredConstructor();
c.setAccessible(true);
Singleton s1 = Singleton.getInstance();
Singleton s2 = c.newInstance(); // 創建第二個實例!
System.out.println(s1 == s2); // false
防禦:在構造函數中檢查實例是否已存在:
private Singleton() {
if (INSTANCE != null) { // 或 Holder.INSTANCE
throw new IllegalStateException("Singleton already initialized");
}
}
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("s.obj"));
oos.writeObject(Singleton.getInstance());
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("s.obj"));
Singleton s2 = (Singleton) ois.readObject(); // 新實例!
防禦:實現 readResolve() 返回現有實例:
private Object readResolve() { return INSTANCE; } // 或 Holder.INSTANCE
| 場景 | 推薦方案 | 理由 |
|---|---|---|
| 一般業務、工具類 | 靜態內部類 | 代碼少、延遲加載、線程安全、無 volatile 複雜度 |
| 高併發核心組件、框架底層 | 枚舉單例 | 極致防禦、零鎖開銷、JVM 級保證 |
| 需延遲加載且 JDK 5 以下環境 | 同步方法/塊 | 犧牲性能換安全,`synchronized getInstance()` |
| Spring 管理 Bean | @Component + @Scope("singleton") | 容器托管,非代碼層面實現 |
public static Singleton getInstance() { return new Singleton(); } — 每次調用新建對象,非單例。SimpleDateFormat、ArrayList) — 單例只保證實例唯一,**不保證線程安全**,需自行同步或用 ThreadLocal。單例模式看似簡單,實則蘊含「類加載機制、內存模型、反射、序列化」等核心知識。**靜態內部類**是手寫代碼的最佳平衡點;**枚舉**是極致防禦的終極形態。實戰中:能用依賴注入框架管理生命週期就不用手寫單例;必須手寫時,首選枚舉,次選靜態內部類,並根據場景決定是否加反射/序列化防禦。