- 有些時(shí)候赊淑,我們想要一個(gè)類在整個(gè)系統(tǒng)中僅存在一個(gè)實(shí)例本砰。比如說碴裙,系統(tǒng)給我們提供了一個(gè)打印機(jī)硬件設(shè)施,但是我們?cè)谙到y(tǒng)中多次new 打印機(jī)点额,創(chuàng)建出多個(gè)打印機(jī)的實(shí)例去完成打印任務(wù)舔株,那么這個(gè)時(shí)候就會(huì)出現(xiàn)資源沖突現(xiàn)象,這就要求我們必須想一個(gè)辦法还棱,去確保系統(tǒng)中存在唯一的一個(gè)打印機(jī)實(shí)例载慈,解決方法有很多種,比如
- 創(chuàng)建一個(gè)全局變量
public static Printer mPrinter = new Printer();
這樣創(chuàng)建一個(gè)全局變量珍手,并且規(guī)定想要使用打印機(jī)服務(wù)办铡,必須使用mPrinter 這個(gè)實(shí)例去完成叶骨,不得再去創(chuàng)建新的打印機(jī)實(shí)例像寒,這種方法是可以解決這個(gè)問題的。但是這種方法是有缺陷的琅翻,比如焙蹭,一些不遵守規(guī)則的人或者說不明白規(guī)則的人又創(chuàng)建了一個(gè)Printer實(shí)例晒杈,還是會(huì)引起沖突,這就出現(xiàn)了單例模式孔厉。
- 單例模式
如圖示拯钻,如若我們將單例模式的構(gòu)造方法設(shè)置成private,也就是私有的撰豺,只提供給一個(gè)獲得實(shí)例的接口粪般,并且在getInstance 方法中做些特殊處理,就可以保證系統(tǒng)中存在唯一實(shí)例污桦,下面介紹幾種單例模式的實(shí)現(xiàn)方法亩歹。
- 經(jīng)典實(shí)現(xiàn)方法(懶漢式)
public class Printer {
private static Printer mPrinter = null;
private Printer(){
}
public static Printer getInstance(){
if(null == mPrinter){
mPrinter = new Printer();
}
return mPrinter;
}
}
通過這種定義方法,就可以保證系統(tǒng)中僅存在唯一實(shí)例凡橱,是嗎小作?
一般情況下,這種方式是不會(huì)存在問題的稼钩,但是因?yàn)槎嗑€程顾稀,導(dǎo)致這種方法是不可靠的。舉例來說坝撑,如果系統(tǒng)中存在兩個(gè)子線程静秆,同時(shí)進(jìn)入 getInstance() 函數(shù)粮揉,當(dāng)一個(gè)線程剛剛通過if判斷語句,還未來得及 new 出一個(gè)實(shí)例抚笔,線程切換扶认,另一個(gè)線程也通過了if判斷語句,那么這個(gè)時(shí)候就會(huì)new出兩個(gè)實(shí)例塔沃,還是無法保證在多線程條件下的單一性蝠引,所以我們需要在getInstance() 函數(shù)前加入 synchronized 關(guān)鍵字。
public static synchronized Printer getInstance(){
if(null == mPrinter){
mPrinter = new Printer();
}
return mPrinter;
}
synchronized 處理蛀柴,可以保證同一時(shí)刻螃概,只有一個(gè)線程可以進(jìn)入getInstance() 函數(shù),但是如果我們需要多次調(diào)用getInstance() 函數(shù)時(shí)鸽疾,這種方法是非常消耗資源的吊洼。當(dāng)然,如果我們只是很少次使用這個(gè)函數(shù)制肮,那么這種方法是完全滿足需求的冒窍。那么針對(duì)多次使用的情況,我們只能使用下一種方式了豺鼻。
- 餓漢式
public class Printer {
private static Printer mPrinter = new Printer();
private Printer(){
}
public static Printer getInstance(){
return mPrinter;
}
}
這種方法我們直接在定義的時(shí)候就 new 出了一個(gè)實(shí)例综液,無論調(diào)用getInstance() 多少次,我們也只會(huì)返回固定的一個(gè)實(shí)例儒飒,也就不存在多線程同步的問題了谬莹。
但是,如果我們從頭到位并沒有用到 Printer 桩了,而我們卻草率的 new 出了一個(gè)實(shí)例附帽,這樣會(huì)占用內(nèi)存資源,所以我們還要用到下一種方法井誉。
- 雙重檢查鎖定
public class Printer {
private static volatile Printer mPrinter = null;
private Printer(){
}
public static Printer getInstance(){
if(null == mPrinter){
synchronized (Printer.class){
if(null == mPrinter){
mPrinter = new Printer();
}
}
}
return mPrinter;
}
}
當(dāng)一個(gè)線程通過第一個(gè)if判斷語句時(shí)蕉扮,無論怎樣,在同一時(shí)刻颗圣,只有一個(gè)線程可以進(jìn)入第二個(gè)if判斷語句喳钟,而且會(huì)new 出一個(gè)實(shí)例,保證synchronized方法從始至終只會(huì)執(zhí)行一次在岂,這樣就解決了餓漢式和懶漢式的尷尬荚藻,因?yàn)樗炔粫?huì)像懶漢式那樣多次進(jìn)入synchronized方法占用系統(tǒng)資源,也不會(huì)像餓漢式那樣在不使用的情況下占用系統(tǒng)資源洁段。
- 一種更好的實(shí)現(xiàn)方法
事實(shí)上還存在一種能夠?qū)I漢式和懶漢式的缺點(diǎn)全部克服而且能將二者的優(yōu)點(diǎn)合二為一的方法,那就是 Initialization on Demand Holder(IoDH)方法共郭。
public class Printer {
private Printer(){
}
public static Printer newInstance(){
return CreatePrinter.mPrinter;
}
private static class CreatePrinter{
private final static Printer mPrinter = new Printer();
}
}
因?yàn)殪o態(tài)的單例對(duì)象沒有作為類的成員變量直接實(shí)例化祠丝,因此在Printer類加載時(shí)并沒有實(shí)例化mPrinter疾呻。第一次調(diào)用newInstance() 的時(shí)候加載內(nèi)部類CreatePrinter ,該內(nèi)部類定義了一個(gè)static 類型的變量mPrinter写半,此時(shí)會(huì)首先初始化這個(gè)變量岸蜗,由JVM 來保證其線程安全性,確保該成員變量只被實(shí)例化一次叠蝇。
可見璃岳,通過使用這個(gè)方法,不僅實(shí)現(xiàn)了延遲加載悔捶,又可以保證線程安全铃慷,不影響系統(tǒng)性能。