Singleton單例模式-【懶漢式-加雙重校驗鎖&防止指令重排序的懶漢式】實現方案中爲什麼需要加volatile關鍵字

前提知識點: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關鍵字?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章