1.餓漢式單例類
class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance() {
return instance;
}
}
當(dāng)類被加載時(shí)亡驰,靜態(tài)變量instance會(huì)被初始化,此時(shí)類的私有構(gòu)造函
數(shù)會(huì)被調(diào)用,單例類的唯一實(shí)例將被創(chuàng)建本冲。如果使用餓漢式單例來實(shí)現(xiàn)EagerSingleton類的設(shè)計(jì),則不會(huì)出現(xiàn)創(chuàng)建多個(gè)單例對(duì)象的情況劫扒,可確保單例對(duì)象的唯一性檬洞。
2.懶漢式單例類與線程鎖定
class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
synchronized public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
該懶漢式單例類在getInstance()方法前面增加了關(guān)鍵字synchronized進(jìn)行線程鎖,以處理多個(gè)線程同時(shí)訪問的問題沟饥。但是添怔,上述代碼雖然解決了線程安全問題,但是每次調(diào)用getInstance()時(shí)都需要進(jìn)行線程鎖定判斷贤旷,在多線程高并發(fā)訪問環(huán)境中广料,將會(huì)導(dǎo)致系統(tǒng)性能大大降低。如何既解決線程安全問題又不影響系統(tǒng)性能呢幼驶?我們繼續(xù)對(duì)懶漢式單例進(jìn)行改進(jìn)艾杏。事實(shí)上,我們無須對(duì)整個(gè)getInstance()方法進(jìn)行鎖定盅藻,只需對(duì)其中的代碼“instance = new LazySingleton();”進(jìn)行鎖定即可购桑。因此getInstance()方法可以進(jìn)行如下改進(jìn):
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
問題貌似得以解決,事實(shí)并非如此氏淑。如果使用以上代碼來實(shí)現(xiàn)單例勃蜘,還是會(huì)存在單例對(duì)象不唯一。原因如下:
假如在某一瞬間線程A和線程B都在調(diào)用getInstance()方法假残,此時(shí)instance對(duì)象為null值缭贡,均能通過instance == null的判斷。由于實(shí)現(xiàn)了synchronized加鎖機(jī)制辉懒,線程A進(jìn)入synchronized鎖定的代碼中執(zhí)行實(shí)例創(chuàng)建代碼阳惹,線程B處于排隊(duì)等待狀態(tài),必須等待線程A執(zhí)行完畢后才可以進(jìn)入synchronized鎖定代碼眶俩。但當(dāng)A執(zhí)行完畢時(shí)莹汤,線程B并不知道實(shí)例已經(jīng)創(chuàng)建,將繼續(xù)創(chuàng)建新的實(shí)例仿便,導(dǎo)致產(chǎn)生多個(gè)單例對(duì)象体啰,違背單例模式的設(shè)計(jì)思想攒巍,因此需要進(jìn)行進(jìn)一步改進(jìn),在synchronized中再進(jìn)行一次(instance == null)判斷荒勇,這種方式稱為雙重檢查鎖定(Double-Check Locking)柒莉。使用雙重檢查鎖定實(shí)現(xiàn)的懶漢式單例類完整代碼如下所示:
class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
//第一重判斷
if (instance == null) {
//鎖定代碼塊
synchronized (LazySingleton.class) {
//第二重判斷
if (instance == null) {
instance = new LazySingleton(); //創(chuàng)建單例實(shí)例
}
}
}
return instance;
}
}
需要注意的是,如果使用雙重檢查鎖定來實(shí)現(xiàn)懶漢式單例類沽翔,需要在靜態(tài)成員變量instance之前增加修飾符volatile兢孝,被volatile修飾的成員變量可以確保多個(gè)線程都能夠正確處理,且該代碼只能在JDK 1.5及以上版本中才能正確執(zhí)行仅偎。由于volatile關(guān)鍵字會(huì)屏蔽Java虛擬機(jī)所做的一些代碼優(yōu)化跨蟹,可能會(huì)導(dǎo)致系統(tǒng)運(yùn)行效率降低,因此即使使用雙重檢查鎖定來實(shí)現(xiàn)單例模式也不是一種完美的實(shí)現(xiàn)方式橘沥。
3.餓漢式單例類與懶漢式單例類比較
餓漢式單例類在類被加載時(shí)就將自己實(shí)例化窗轩,它的優(yōu)點(diǎn)在于無須考慮多線程訪問問題,可以確保實(shí)例的唯一性座咆;從調(diào)用速度和反應(yīng)時(shí)間角度來講痢艺,由于單例對(duì)象一開始就得以創(chuàng)建,因此要優(yōu)于懶漢式單例介陶。但是無論系統(tǒng)在運(yùn)行時(shí)是否需要使用該單例對(duì)象堤舒,由于在類加載時(shí)該對(duì)象就需要?jiǎng)?chuàng)建,因此從資源利用效率角度來講哺呜,餓漢式單例不及懶漢式單例舌缤,而且在系統(tǒng)加載時(shí)由于需要?jiǎng)?chuàng)建餓漢式單例對(duì)象,加載時(shí)間可能會(huì)比較長(zhǎng)某残。
懶漢式單例類在第一次使用時(shí)創(chuàng)建国撵,無須一直占用系統(tǒng)資源,實(shí)現(xiàn)了延遲加載驾锰,但是必須處理好多個(gè)線程同時(shí)訪問的問題卸留,特別是當(dāng)單例類作為資源控制器走越,在實(shí)例化時(shí)必然涉及資源初始化椭豫,而資源初始化很有可能耗費(fèi)大量時(shí)間,這意味著出現(xiàn)多線程同時(shí)首次引用此類的機(jī)率變得較大旨指,需要通過雙重檢查鎖定等機(jī)制進(jìn)行控制赏酥,這將導(dǎo)致系統(tǒng)性能受到一定影響。