雙重檢查鎖旨在實現線程安全的延遲初始化單例,同時避免每次獲取實例都同步的效能開銷。
public class Singleton {
private volatile Singleton instance; // 關鍵:volatile
public Singleton getInstance() {
if (instance == null) { // 第一次檢查(無鎖)
synchronized (this) { // 加鎖
if (instance == null) { // 第二次檢查(鎖內)
instance = new Singleton(); // 初始化
}
}
}
return instance;
}
}
instance = new Singleton() 非原子操作,實際包含三步:
JVM/JIT 編譯器為優化效能,可能重排為 1 → 3 → 2。線程 A 執行到步驟 3 時,instance 已非 null,線程 B 進入第一次檢查通過,直接返回未初始化完成的對象,導致空指針或數據錯誤。
| JDK 版本 | 狀態 | 說明 |
|---|---|---|
| JDK 1.4 及之前 | 失效 | 內存模型缺陷,volatile 不保證有序性 |
| JDK 5 (JSR-133) | 修復 | 新內存模型強化 volatile 語義,DCL 正常工作 |
| JDK 6+ | 穩定 | 逃逸分析、標量替換等優化可能進一步影響 |
// 錯誤 1:缺少 volatile
private Singleton instance;
// 錯誤 2:鎖錯誤對象
synchronized (Singleton.class) { ... } // 類鎖可行但 this 更精確
// 錯誤 3:第一次檢查在鎖內(退化為同步方法)
public Singleton getInstance() {
synchronized (this) {
if (instance == null) instance = new Singleton();
}
}
// 錯誤 4:嘗試用於 long/double 非原子類型
private volatile long value; // 基本類型長雙非原子,DCL 不適用
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
優點:線程安全(類加載機制保證)、延遲初始化、代碼簡潔、無同步開銷、無 volatile 複雜性。
推薦等級:⭐⭐⭐⭐⭐ (首選)
public enum Singleton {
INSTANCE;
public void doSomething() {}
}
優點:防序列化破壞、防反射攻擊、線程安全、語法最簡潔。
推薦等級:⭐⭐⭐⭐⭐ (Effective Java 作者 Josh Bloch 推薦)
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; }
}
適用:初始化開銷小、一定會被使用的場景。
public synchronized static Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
缺點:每次調用同步,效能差。
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class SingletonBenchmark {
@Benchmark
public Singleton testDCL() { return DCLSingleton.getInstance(); }
@Benchmark
public Singleton testHolder() { return HolderSingleton.getInstance(); }
@Benchmark
public Singleton testEnum() { return EnumSingleton.INSTANCE; }
}
典型結果:
// DCL/Holder 均可被反射破壞
Constructor c = Singleton.class.getDeclaredConstructor();
c.setAccessible(true);
Singleton s1 = c.newInstance();
Singleton s2 = c.newInstance(); // s1 != s2
// 枚舉天然免疫
// 非枚舉需在構造器檢查
private Singleton() {
if (instance != null) throw new IllegalStateException("單例已存在");
}
implements Serializable {
// 必須實現 readResolve
private Object readResolve() { return INSTANCE; }
}
Spring `@Singleton`、Guice `@Singleton`、Dagger `@Singleton` — 框架層面管理生命週期,業務代碼無需手寫單例。
「雙重檢查鎖是理解 Java 內存模型的絕佳切入點,但在現代 Java 開發中,靜態內部類與枚舉單例是更優、更安全、更易維護的選擇。」
volatile 禁止指令重排