單例模式確保一個類在全局僅有一個實例,並提供全局訪問點。它是資源管理(連接池、配置管理器、日誌記錄器等)的首選模式。本文系統剖析 Java 中單例的三大經典實現——餓漢式、懶漢式、登記式,深度解析雙重檢查鎖(DCL)的線程安全機制、類載入器陷阱、垃圾回收陷阱,並提供生產級配置管理器實戰案例。
getInstance()
public class EagerSingleton {
private EagerSingleton() {} // 私有建構子
private static final EagerSingleton INSTANCE = new EagerSingleton();
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
UML:
特點:
public class LazySingleton {
private LazySingleton() {}
private static volatile LazySingleton instance = null; // 關鍵:volatile
public static LazySingleton getInstance() {
if (instance == null) { // 第一次檢查:無鎖,效能最佳
synchronized (LazySingleton.class) { // 類級別鎖
if (instance == null) { // 第二次檢查:防止重複建立
instance = new LazySingleton();
}
}
}
return instance;
}
}
UML:
關鍵點深度解析:
| 元素 | 作用 | 缺失後果 |
|---|---|---|
volatile | 禁止指令重排序,保證構造完成前不對其他線程可見 | 其他線程可能看到「半初始化」物件 |
第一次 if (instance == null) | 避免不必要的同步開銷 | 每次調用都進入同步塊,效能大幅下降 |
第二次 if (instance == null) | 防止多線程同時通過第一次檢查後重複建立 | 建立多個實例,違反單例語義 |
synchronized (LazySingleton.class) | 類級別鎖,保護關鍵區 | 無法保證原子性 |
⚠️ 歷史遺留問題:JDK 1.4 及之前版本的記憶體模型有缺陷,DCL 可能失效。JDK 5+ 引入 volatile 語義修復後,DCL 才真正線程安全。
public class RegSingleton {
protected RegSingleton() {} // protected 允許子類建構
private static final Map REGISTRY = new HashMap<>();
static {
// 自我註冊
RegSingleton singleton = new RegSingleton();
REGISTRY.put(singleton.getClass().getName(), singleton);
}
public static synchronized RegSingleton getInstance(String name) {
if (name == null) name = RegSingleton.class.getName();
if (!REGISTRY.containsKey(name)) {
try {
REGISTRY.put(name, Class.forName(name).newInstance());
} catch (Exception e) { /* 處理異常 */ }
}
return REGISTRY.get(name);
}
}
// 子類實現
public class RegSingletonChild extends RegSingleton {
public static RegSingletonChild getInstance() {
return (RegSingletonChild) RegSingleton.getInstance(RegSingletonChild.class.getName());
}
}
UML:
優缺點權衡:
new 破壞單例
public class ObjectKeeper extends Thread {
private static final ObjectKeeper KEEPER = new ObjectKeeper();
private static final Vector
UML:
使用方式:
ObjectKeeper.keepObject(ConfigManager.getInstance());
// ... 系統運行 ...
ObjectKeeper.discardObject(ConfigManager.getInstance());
需求:單例管理 singleton.properties,支援運行時檢測檔案修改並熱重載。
public class ConfigManager {
private static final String PFILE = System.getProperty("user.dir") + "/Singleton.properties";
private static final ConfigManager INSTANCE = new ConfigManager(); // 餓漢式
private File file;
private long lastModified;
private Properties props;
private ConfigManager() {
file = new File(PFILE);
lastModified = file.lastModified();
reload();
}
public static ConfigManager getInstance() {
return INSTANCE;
}
public Object getConfigItem(String name, Object defaultVal) {
long currentModified = file.lastModified();
// 檢測檔案變更
if (currentModified > lastModified) {
reload();
lastModified = currentModified;
} else if (currentModified == 0) {
// 檔案被刪除
return defaultVal;
}
Object val = props.getProperty(name);
return val != null ? val : defaultVal;
}
private void reload() {
props = new Properties();
try (FileInputStream fis = new FileInputStream(file)) {
props.load(fis);
} catch (IOException e) { /* 記錄日誌 */ }
}
}
node1.item1=How
node1.item2=are
node2.item1=you
node2.item2=doing
node3.item1=?
通過以下指標驗證單例唯一性:
System.identityHashCode(instance)instance.getClass().getClassLoader()| 場景 | 推薦實現 | 理由 |
|---|---|---|
| 一般業務單例、資源管理器 | 餓漢式 | 簡單、安全、JVM 保證線程安全、無同步開銷 |
| 實例化極其耗時/資源、且可能不被使用 | 懶漢式 (DCL + volatile) | 延遲初始化,節省啟動資源 |
| 需支援單例繼承體系、插件架構 | 登記式 | 允許子類擴展,運行時動態註冊 |
| JDK 1.1 環境、或自定義類載入器環境 | 餓漢式 + ObjectKeeper | 防止類被意外卸載 |
現代最佳實踐:優先考慮 枚舉單例(Effective Java Item 3)——天然線程安全、防序列化破壞、防反射攻擊、代碼最簡潔:
public enum EnumSingleton {
INSTANCE;
public void doSomething() { ... }
}