提起單例模式,作爲攻城獅的你我都不會感覺到陌生,而爲了確保在程序中的線程安全,我們常常會傾向於雙重校驗和靜態類兩種方式。而且衆所周知,在雙重校驗的方式中,我們發現了關鍵字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修飾之後,爲什麼就可以避免上圖中多線程訪問的問題,將在下篇中講解,敬請期待!