之前在很多單例類中看到雙重鎖定檢查(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;
}
}