單例模式雙重檢驗鎖的volatile和兩次判空

首先是代碼,經典的雙重鎖寫法

public class Singleton {
    private volatile static Singleton Instance;
    private Singleton(){}
    public static Singleton getInstance(){
        if(Instance == null){
            synchronized(Singleton.class){
                if(Instance == null){
                    Instance = new Singleton();
                }
            }
        }
        return Instance;
    }
}

先說爲什麼需要兩次判空的原因?

第一次判斷是爲了驗證是否創建對象,避免多線程訪問時每個線程都加鎖,提升效率第二次判斷是爲了避免重複創建單例,因爲可能會存在多個線程通過了第一次判斷在等待鎖,來創建新的實例對象。
例如:有三個線程,A與B同時調用getSingleton時,判斷第一個if都爲空,這時A拿到鎖,進行第二層if判斷,條件成立new了一個對象;由於Synchronized原因,B在外層等待,A創建完成,釋放鎖,B拿到鎖,進行第二層if判斷,條件不成立,結束釋放鎖。C調用getSingleton時第一層判斷不成立,直接拿到singleton對象返回,避免進入鎖,減少性能開銷。

在說說爲什麼有了Synchronized卻還需要volatile去修飾Instance。

volatile修飾變量只是爲了禁止指令重排序,因爲在 Instance = new Singleton(); 創建對象時,底層會分爲四個指令執行:(下面是正確的指令執行順序)
①、如果類沒有被加載過,則進行類的加載
②、在堆中開闢內存空間 adr,用於存放創建的對象
③、執行構造方法實例化對象
④、將堆中開闢的內存地址 adr 賦值給被volatile修飾的Instance引用變量
如果Instance引用變量不使用volatile修飾的話,則可能由於編譯器和處理器對指令進行了重排序,導致第④步在第③步之前執行,此時Instance引用變量不爲null了,但是Instance這個引用變量所指向的堆中內存地址中的對象是還沒被實例化的,實例對象還是null的;那麼在第一次判空時就不爲null了,然後去使用時就會報NPE空指針異常了。

原文鏈接:https://blog.csdn.net/weixin_45398467/article/details/108893962

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