二十二、應用雙重鎖定檢查於單例模式中的問題

之前在很多單例類中看到雙重鎖定檢查(DCL),也聽到過兩種聲音:第一種聲音是希望只在第一次創建實例時進行同步,於是纔有兩次判斷instance是否爲null的判斷;另一種聲音是雙重鎖定檢查用在這裏根本起不到預想的作用。今天終於知道後一種說法的原因了。

public class SingletonPattern {
	private static SingletonPattern instance = null;
	private SingletonPattern(){
	}
	public static SingletonPattern getInstance(){
		if(instance == null){
			synchronized(SingletonPattern.class){
				if(instance == null){
					instance = new SingletonPattern();
				}
			}
		}
		return instance;
	}
}
    看上去貌似是只有在對象還不存在時即第一次創建對象時纔會走到synchronized(SingletonPattern.class),instance已經存在了的話,在第一個if(instance == null)時就不滿足所以不會進入synchronized(SingletonPattern.class)。真的是這樣嗎?
    首先來看這句instance = new SingletonPattern();這條語句被編譯後形成了多條彙編指令,主要做如下三個動作:
1.給SingletonPattern的實例分配內存。
2.初始化SingletonPattern的構造器。
3.將instance對象只想分配的內存空間。
    這些彙編指令在JVM中執行,由於Java編譯器允許處理器亂序執行,所以上面的動作2和3的順序是無法保證的,有可能是123也可能是132。如果是132的情況,如果第一個線程在3執行完2未執行前,就被切換到第二個線程上,這時instance因爲已經在線程一中執行了動作3,instance已經是費空了,所以線程二直接用這個instance,而這個instance實際上還沒有被構造完成,就會出錯了。
    如果一定要用雙重鎖定檢查來實現單例模式,那在JDK 1.5及之後版本中,可以將instance的聲明改成private volatile static SingletonPattern instance = null;就是加了個volatile,這就保證每次用instance都是從主內存中讀取,這樣就可以使用雙重鎖定檢查來完成單例模式了。當然volatile或多或少也會影響到性能。所以索性不如用下面這種實現了:
public class SingletonPattern2 {
	private static final SingletonPattern2 singletonPattern = new SingletonPattern2();
	private SingletonPattern2(){
	}
	public synchronized static SingletonPattern2 getInstance(){
		return singletonPattern;
	}
}
發佈了42 篇原創文章 · 獲贊 0 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章