為什么雙重檢查會帶來空指針異常問題?
if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } }
}
instance = new Singleton();
上述加粗的代碼并不是原子操作,包含三個步驟:
-
為 Singleton 分配內存地址
-
執行 Singleton() 構造方法,初始化成員變量等
-
將內存地址賦值給 instance 變量
假設線程 A 正在執行:
instance = new Singleton();
由于指令重排序,線程 A 可能在構造函數執行之前,就已經將內存地址賦值給 instance,導致 instance 變為非 null。此時線程 B 會認為 instance 已經是合法對象,于是直接使用,結果可能:
-
訪問尚未初始化的字段
-
拋出 NullPointerException
-
出現邏輯錯誤或不可預知的行為
為什么可以通過靜態內部類實現懶漢式單例模式?
public class Singleton { private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; }
}
這依賴于 Java 類加載機制的特性:
-
JVM 在加載外部類的過程中不會加載靜態內部類,只有內部類的屬性 / 方法被調用時才會進行加載(懶漢式)
-
JVM 在加載類的時候會自動保證線程安全,一個類在 JVM 中只會被加載一次