相信大家對單例模式應該不陌生,每個人都能寫出好多種單例模式的實現,總結來說就有五種:懶漢、餓漢、靜態內部類、枚舉和雙重檢查鎖定。針對這幾種方式的代碼,可以在網上搜索到,這里就不再細說。
最近又看了一遍單例的幾種實現方式,發現了一些其他東西。先看以下單例模式的實現代碼
public?classSingleton2?{
privateSingleton2(){}
private?staticSingleton2instance;
public?static?synchronizedSingleton2?getInstance()?{
if(instance==null)?{
instance=newSingleton2();
}
returninstance;
}
}
這種實現方式利用了同步的方式保證線程安全,但是效率特別低下,同步鎖要善用。
為了提高效率,可以把同步鎖放到方法里,先判斷instance是否為空,若不為空加鎖進行初始化,若為空就可以直接獲取對象,這樣可以減少一大部分的線程等待,代碼如下:
public?classSingleton7?{
privateSingleton7(){}
private?staticSingleton7instance;
public?staticSingleton7?getInstance()?{
if(instance==null)?{
synchronized(Singleton7.class)?{
if(instance==null)?{
instance=newSingleton7();
}
}
}
returninstance;
}
}
現在提個問題,以上代碼真的正確嗎?覺得代碼沒問題的人,可以繼續往下看,能看出問題的,估計下面的東西也知道。
這里先把問題點明,以上代碼中這一行private?staticSingleton7instance;是有問題的,缺少了修飾符volatile,這個可是很重要的。
這里先說明類初始化大概的流程:
1、分配內存空間
2、初始化對象
3、設置instance指向分配的內存地址
但是在這三步中,第二步和第三步是可以調換順序的,也即
1、分配內存空間
3、設置instance指向分配的內存地址
2、初始化對象
基于上面的理論,以上代碼在缺少volatile的情況下,會有以下問題
從圖中大致能看出B線程雖然獲取到了instance對象,但這個對象其實沒有真正初始化。
知道這個問題之后,volatitle在這里就能起到至關重要的作用了,使用了volatitle之后,在JDK1.5之后,類初始化流程中第二步和第三步在多線程的情況會被禁止,從而保證代碼的正常執行。