讀了本文,你會知道,為什么不加volatile關鍵字的單例模式不是線程安全的
有經驗的開發者都知道雙重鎖定檢查(DCL,Double Check Lock)的單例是最優秀的,如下文所示:
1 public class Singleton { 2 private static Singleton instance = null; 3 public static Singleton getInstance() { 4 if(null == instance) { // 第一次檢查 5 synchronized (Singleton.class) { 6 if(null == instance) { // 加鎖后第二次檢查 7 instance = new Singleton(); 8 } 9 } 10 } 11 12 return instance; 13 14 } 15 }
這看上去一切都很完美,無懈可擊,但實際上這個 getInstance() 方法並不完美。問題出在哪里呢?出在 new 操作上,我們以為的 new 操作應該是:
- 分配一塊內存 M;
- 在內存 M 上初始化 Singleton 對象;
- 然后 M 的地址賦值給 instance 變量。
但是實際上,經過編譯器優化后的執行順序是這樣的:
- 分配一塊內存 M;
- 將 M 的地址賦值給 instance 變量;
- 最后在內存 M 上初始化 Singleton 對象。
如下圖所示,線程A進入<第一次檢查>,A先獲得Synchronize鎖,分配一塊內存M,先將M的地址賦值給了 instance 變量, 此刻發生線程切換,線程B檢測到instance不為空,直接返回未初始化的instance, 如果我們這個時候訪問 instance 的成員變量就可能觸發空指針異常。
所以,我們會加對象上加 Volatile關鍵字,禁止指令重排序,
Volatile的原理,可以參考我的另一篇文章:Java多線程的volatile底層實現原理