單例模式是最經典的設計模式之一,目標是確保一個類在全局範圍內只有一個實例,並提供一個全局訪問點。雖然教科書上常見的寫法看似簡單,但在多線程環境下卻暗藏隱患。本文將從最基礎的實現開始,逐步演進到線程安全、高效能的最佳實踐。
最直觀的寫法是在首次調用時創建實例:
public class Singleton {
private Singleton() {}
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
致命缺陷: 當多線程並發調用 getInstance() 時,多個線程可能同時通過 instance == null 判斷,導致創建多個實例,違背單例初衷。
public static synchronized Singleton getInstance() {
if (instance == null) {
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,防止指令重排序導致半初始化對象被其他線程看到。JDK 1.5+ 後此寫法才真正安全。
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
優點: 利用類加載機制天然保證線程安全,無鎖、延遲初始化、程式碼簡潔,是業界公認的最佳實踐之一。
public enum Singleton {
INSTANCE;
public void doSomething() {}
}
優點: 不僅線程安全,還能天然防止反射攻擊(構造器私有)和反序列化產生新實例,《Effective Java》強力推薦。
以下測試代碼模擬多線程環境驗證單例性:
public class Test {
public static void main(String[] args) {
Runnable task = () -> {
Singleton s = Singleton.getInstance();
System.out.println(Thread.currentThread().getId() + ": " + s.hashCode());
};
new Thread(task).start();
new Thread(task).start();
}
}
輸出顯示兩個線程獲取到的實例 hashCode 相同,證明為同一對象。
volatile)。