單例模式確保一個類在全局範圍內有且僅有一個實例,並提供全局訪問點。適用場景:窗口管理器、配置管理器、數據庫連接池、日誌記錄器等「全局唯一」資源。核心三要素:私有構造器、靜態實例引用、公開靜態獲取方法。
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 = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) instance = new Singleton(); // 多線程下會創建多個實例
return instance;
}
}
多線程並發進入 if 判斷導致多次實例化。
public static synchronized Singleton getInstance() { ... }
每次調用都加鎖,性能損耗大。
public static Singleton getInstance() {
synchronized (Singleton.class) { ... } // 鎖類對象,非 this
}
public class Singleton {
private static volatile Singleton instance = null; // volatile 禁止指令重排
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查,無鎖讀取
synchronized (Singleton.class) { // 加鎖
if (instance == null) { // 第二次檢查
instance = new Singleton(); // 實例化
}
}
}
return instance;
}
}
關鍵點:
volatile確保new Singleton()的三步操作(分配內存、初始化對象、賦值引用)不被重排序,防止其他線程看到「半初始化」對象。JDK 1.5+ 記憶體模型修復後 DCL 完全可靠。
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() { return Holder.INSTANCE; }
}
利用類加載機制:外部類加載時不初始化內部類,首次調用 getInstance() 才觸發內部類加載初始化,天然線程安全。
public enum Singleton { INSTANCE; }
《Effective Java》強力推薦,天然序列化安全、反射無法破壞構造器。
// 普通類:創建兩個對象,屬性獨立
Person p1 = new Person(); p1.setName("Jack"); p1.setAge(18);
Person p2 = new Person(); p2.setName("David"); p2.setAge(20);
// 輸出:Jack:18 / David:20
// 單例類:獲取同一實例,後設置覆蓋前設置
Person p1 = Person.getInstance(); p1.setName("Jack"); p1.setAge(18);
Person p2 = Person.getInstance(); p2.setName("David"); p2.setAge(20);
// 輸出:David:20 / David:20 (同一對象,狀態被覆蓋)
| 場景 | 推薦寫法 |
|---|---|
| 一般業務、需延遲加載 | 靜態內部類 / 雙重檢查鎖 |
| 高併發、性能敏感 | 枚舉 / 靜態內部類 |
| 需防反射/反序列化破壞 | 枚舉 |
| 簡單工具類、無延遲需求 | 餓漢式 |
單例模式看似簡單,實則考驗對 Java 內存模型、類加載機制、線程同步的深度理解。從餓漢到 DCL 再到枚舉,每種寫法都有其適用邊界。生產環境首選枚舉或靜態內部類,兼顧線程安全、延遲加載、防攻擊與代碼簡潔。切記:單例保證的是實例唯一性,不代表狀態線程安全,可變狀態仍需額外同步。