前提知識點:volatile可以保證可見性+防止指令重排序,synchronized可以保證可見性+防止指令重排序+原子性。
也即是說volatile是synchronized的功能子集,我們知道在【懶漢式-加雙重校驗鎖】的單例模式實現中已經使用了synchronized關鍵字,那爲什麼還需要加volatile關鍵字呢
回顧【懶漢式-加雙重校驗鎖&防止指令重排序的懶漢式】
public class MyManger3 {
private static volatile MyManger3 instance;
private MyManger3() {
}
public static MyManger3 getInstance() {
if(instance==null){ //a
synchronized (MyManger3.class){ //b
if(instance==null){ //c
instance=new MyManger3(); //d
}
}
}
return instance;
}
}
---------------------
作者:明月(Alioo)
來源:CSDN
原文:https://blog.csdn.net/hl_java/article/details/70148622
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!
在回答這個問題之前你需要知道的知識點
instance=new MyManger3();
這個語句不是一個原子操作,編譯後會多條字節碼指令:
- 步驟1.爲new出來的對象開闢內存空間
- 步驟2.初始化,執行構造器方法的邏輯代碼片段
- 步驟3.完成instance引用的賦值操作,將其指向剛剛開闢的內存地址
可能場景-線程t1,t2均到達 代碼b處
這個時候假若線程t1獲得鎖,t2處於阻塞狀態,直到t1 依次執行代碼a,b,c,d,並且在釋放鎖之前會將對變量instance的修改刷新到主存當中,保證當其他線程再進入的時候,在主存中讀取到的就是最新的變量內容了。
t1釋放鎖之後,t2獲得鎖,根據重新從主內存拿到的變量instance值判斷不爲null,則直接跳過代碼d的執行,即線程2只執行了代碼a,b,c就釋放掉了鎖。
結論:這個場景下線程t1,t2會拿到了一個完整的instance所以是不存在問題的。
真正的問題場景-線程t1執行到代碼d處,線程t2執行到代碼a處
線程t1執行到代碼d處時,在沒有加volatile關鍵字修飾instance時是存在指令重排序的問題的,假若代碼d的執行順序是步驟1、步驟3、步驟2。
在線程t1執行完成步驟3,還沒有執行步驟2時,線程t2執行到代碼a處,對instance進行判斷是否爲null,發現不爲null則直接返回使用(但此時instance是不一個不爲null的但是沒有初始化完成的對象)
結論:這個場景下線程t1是沒有問題的會得到一個完整的instance,但是t2會提前拿到了一個不完整的instance是存在問題的,所以需要加上volatile來禁止這個語句instance=new MyManger3();
進行指令重排序。
參考文章
https://blog.csdn.net/xiakepan/article/details/52444565
https://www.cnblogs.com/damonhuang/p/5431866.html 這篇文章記得看下評論區
作者相關文章
Singleton單例模式的幾種創建方法
Singleton單例模式-如何防止JAVA反射對單例類的攻擊?
Singleton單例模式-如何防止序列化對單例類的攻擊?
Singleton單例模式-【懶漢式-加雙重校驗鎖&防止指令重排序的懶漢式】實現方案中爲什麼需要加volatile關鍵字?