單例模式確保類在全局僅有一個實例,並提供全局訪問點。在多線程環境下,必須保證實例化過程的線程安全。本文總結七種線程安全單例實現,從餓漢式到枚舉,分析優劣與適用場景。
類加載時即創建實例,天然線程安全(JVM 保證類初始化原子性)。
// 方式 A:靜態常量
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; }
}
// 方式 B:靜態代碼塊(可添加異常處理邏輯)
public class Singleton {
private static Singleton instance;
private Singleton() {}
static {
try { instance = new Singleton(); }
catch (Exception e) { throw new RuntimeException("初始化失敗", e); }
}
public static Singleton getInstance() { return instance; }
}
優點:簡單、無鎖、性能最高。
缺點:類加載即創建,無延遲加載能力;若實例從未使用則浪費內存。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
}
缺點:鎖粒度過大,每次調用均同步,性能極差。
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
缺點:同步塊內仍包含非關鍵代碼,性能未本質提升。
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查
synchronized (Singleton.class) {
if (instance == null) { // 第二次檢查
instance = new Singleton();
}
}
}
return instance;
}
關鍵:instance 必須聲明為 volatile,防止指令重排導致半初始化對象被其他線程看到。
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() { return Holder.INSTANCE; }
}
原理:利用類加載機制,外部類加載時不初始化內部類,首次調用 getInstance() 觸發內部類加載,JVM 保證線程安全。
優點:延遲加載、無鎖、代碼簡潔,推薦生產環境使用。
public enum Singleton {
INSTANCE;
public void doSomething() { System.out.println("執行業務邏輯"); }
}
優點:語法簡潔、天然防反射攻擊、防序列化破壞、JVM 保證線程安全。缺點:無法延遲加載(枚舉類加載即實例化)、無法繼承其他類。
若單例實現 Serializable,反序列化會創建新實例,需添加 readResolve() 方法:
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private static class Holder { static final Singleton INSTANCE = new Singleton(); }
private Singleton() {}
public static Singleton getInstance() { return Holder.INSTANCE; }
// 反序列化時返回唯一實例
protected Object readResolve() { return Holder.INSTANCE; }
}
| 實現方式 | 線程安全 | 延遲加載 | 性能 | 防反射 | 防序列化破壞 | 推薦指數 |
|---|---|---|---|---|---|---|
| 餓漢式(常量/靜態塊) | ✓ | ✗ | 極高 | ✗ | ✗ | ★★★☆☆ |
| synchronized 方法 | ✓ | ✓ | 低 | ✗ | ✗ | ★☆☆☆☆ |
| 同步代碼塊 | ✓ | ✓ | 低 | ✗ | ✗ | ★☆☆☆☆ |
| DCL (volatile) | ✓ | ✓ | 高 | ✗ | ✗ | ★★★★☆ |
| 靜態內部類 | ✓ | ✓ | 高 | ✗ | ✗ | ★★★★★ |
| 枚舉 | ✓ | ✗ | 高 | ✓ | ✓ | ★★★★★ |
| 內部類+readResolve | ✓ | ✓ | 高 | ✗ | ✓ | ★★★★☆ |
readResolve()。單例模式看似簡單,多線程下正確實現需考慮內存可見性、指令重排、反射攻擊、序列化破壞等細節。掌握上述七種寫法的原理與優劣,能根據業務場景做出最優架構決策。