單例模式雙重校驗的再理解

        提起單例模式,作爲攻城獅的你我都不會感覺到陌生,而爲了確保在程序中的線程安全,我們常常會傾向於雙重校驗和靜態類兩種方式。而且衆所周知,在雙重校驗的方式中,我們發現了關鍵字volatile的身影,而且一直以來小編只是知道 該關鍵字可以保證操作之間的可見性。但是隻知其一啊,今天突然明白這其中的道理:

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}   
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
} 
      如上述代碼片所示,singleton變量使用了volatile關鍵字修飾,也就意味着這個變量對接下來的操作具有可見性(原因稍後會有解釋)。

♗  如果上述代碼中singleton變量去掉volatile關鍵字……

public class Singleton {  
    private static Singleton singleton;  
    private Singleton (){}   
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
} 

     如上述代碼所示,如果是單線程操作,由於代碼的順序間接的決定了執行順序,而且在單線程中,即使jvm執行了順序重排,仍然不會出現問題;

    在討論多線程的場景之前,我們先來科普一下 對象初始化的過程:在對象初始化也就是如第八行代碼(singleton = new Singleton();  )所示,要知道,這行代碼一共有三個過程:

    分配對象的內存空間-->初始化對象 --> 將singleton指向剛分配好的內存地址

-----------------------------------------我是分割線-------------------------------------------

    明白初始化的過程之後,我們開始討論多線程的場景:假設現在有線程A和線程B,當兩個線程同時來訪問Singleton對象,但是在訪問期間會有以下不安全的情況:

1)A /B 線程同時訪問,這時兩個線程都發現singleton爲空,所以兩個線程都會創建一個singleton變量,這自然不符合單例模式的初衷……

2)在不同的時間,A、B線程分別來訪問這個Singleton對象,可能會出現報錯的情況:

            

     如上圖所示,線程B在T4時間的訪問一定會出現NullPointerException,因爲找不到這個對象噻!

     關於volatile修飾之後,爲什麼就可以避免上圖中多線程訪問的問題,將在下篇中講解,敬請期待!       

                        


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