雙重檢查單例模式,單例對象爲何要加上volatile關鍵字?

代碼:

class Singleton{
    private volatile static Singleton instance = null;
    
    private Singletion{}

    public static Singleton getInstance(){
        if(instance==null){                        //#1
            synchronized(Singleton.class){         //#2
                if(instance==null){                //#3
                    instance = new Singletion();   //#4
                }else{
                    System.out.println("test");    //#5
                }
            }
        }
        return instance;
    }

}

 

單例對象爲何要加上volatile關鍵字?有如下兩方面原因:

(1)保證可見性:

如果"private static Singleton instance = null;":

1. thread1進入#1, 這時thread1的instance爲null,thread1讓出CPU資源給thread2.
2. thread2進入#1, 這時thread2的instance爲null,thread2讓出CPU資源給thread1.
3. thread1會依次執行#2,#3,#4,最終在thread1裏面實例化了instance。thread1執行完畢讓出CPU資源給thread2.
4. thread2接着#1跑下去,跑到#3的時候,由於#1裏面拿到的instance還是null(並沒有及時從thread1裏面拿到最新的),所以thread2仍然會執行#3,#4.
5. 最後thread1和thread2都實例化了instance.

 

如果"private volatile static Singleton instance = null;":

1. thread1進入#1, 這時thread1的instance爲null(java內存模型會從主內存裏拷貝一份instance(null)到thread1),thread1讓出CPU資源給thread2.
2. thread2進入#1, 這時thread2的instance爲null(java內存模型會從主內存裏拷貝一份instance(null)到thread2), thread2讓出CPU資源給thread1.
3. thread1會依次執行#2,#3,#4, 最終在thread1裏面實例化了instance(由於是volatile修飾的變量,會馬上同步到主內存的變量去)。thread1執行完畢讓出CPU資源給thread2.
4. thread2接着#1跑下去,跑到#3的時候,會又一次從主內存拷貝一份instance(!=null)回來,所以thread2就直接跑到了#5.
5. 最後thread2不會再重複實例化instance了.

 

(2)防止重排序:

在單例模式中,Instance instance= new Instance();   這一句代碼不是原子操作,它可以分成三步原子指令:

1.分配內存地址;

2.new一個Instance對象;

3.將內存地址賦值給instance;

CPU爲了提高執行效率,這三步操作的順序可以是123,也可以是132。如果是132順序的話,當把內存地址賦給inst後,inst指向的內存地址上面還沒有new出來單例對象,這時候如果就拿到instance的話,它其實就是空的,會報空指針異常。

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