單例模式確保類全局唯一實例,是創建型模式中最基礎、面試最高頻的設計模式。本文系統梳理五種經典實現,從線程不安全到線程安全、從急切創建到延遲加載,層層遞進,並深度解析雙重檢查鎖(DCL)的原理與 volatile 關鍵作用。
| 模式 | 線程安全 | 延遲加載 | 性能 | 複雜度 | 推薦度 |
|---|---|---|---|---|---|
| 1. 懶漢式(基礎版) | ❌ | ✅ | 高 | 低 | ⭐ |
| 2. 懶漢式(同步方法) | ✅ | ✅ | 低 | 低 | ⭐⭐ |
| 3. 餓漢式 | ✅ | ❌ | 高 | 低 | ⭐⭐⭐ |
| 4. 靜態內部類 | ✅ | ✅ | 高 | 中 | ⭐⭐⭐⭐ |
| 5. 雙重檢查鎖 (DCL) | ✅ | ✅ | 高 | 高 | ⭐⭐⭐⭐⭐ |
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() {}
// 內部類只有在調用 getInstance() 時才會被加載、初始化
private static class Holder {
static final Singleton4 INSTANCE = new Singleton4();
}
public static Singleton4 getInstance() {
return Holder.INSTANCE;
}
}
原理:利用類加載機制保證線程安全,利用內部類延遲加載特性實現懶加載。無鎖、高性能、代碼簡潔。
public class Singleton5 {
// volatile 禁止指令重排,保證可見性
private volatile static Singleton5 instance;
private Singleton5() {}
public static Singleton5 getInstance() {
if (instance == null) { // 第一次檢查:無鎖,性能高
synchronized (Singleton5.class) { // 類級別鎖
if (instance == null) { // 第二次檢查:防止重複創建
instance = new Singleton5();
}
}
}
return instance;
}
}
instance = new Singleton5() 非原子操作,JVM 可能重排為:
若發生 2↔3 重排,線程 A 執行完步驟 3 但未完成步驟 2,線程 B 進入第一次檢查發現 instance != null,直接返回半初始化對象,導致崩潰。
volatile 禁止指令重排 + 保證可見性,強制執行順序 1→2→3,解決該問題。
if (instance == null):否則退化為同步方法if (instance != null) throw ExceptionreadResolve() 返回現有實例public enum SingletonEnum {
INSTANCE;
public void doSomething() { ... }
}
// 使用:SingletonEnum.INSTANCE.doSomething();
優勢:天然防反射、防序列化破壞、代碼極簡、JVM 保證線程安全。
工程實踐建議:優先靜態內部類,極致性能場景用 DCL,簡單場景用枚舉。理解 DCL 中 volatile 的作用,是掌握 Java 內存模型、指令重排、Happens-Before 規則的絕佳切入點。