寫在前面#
其實(shí)我也不知道想說什么括袒,來簡書已經(jīng)足足15天了次兆,每天都在通過文字總結(jié)和分享自己的所學(xué)知識,寫技術(shù)文章好像也已經(jīng)成了生活中的一部分了锹锰,但是芥炭,更重要的是,我要告訴你們恃慧,作為一只異地戀狗在大街上看見別的情侶秀恩愛园蝠,真的會(huì)超想她,有沒有和我一樣的同胞(掩面失聲痛哭)痢士,如果我的心情一片黑暗彪薛,我將用學(xué)習(xí)將它照亮!
單例模式####
單例模式(Singleton pattern)是一種常用的軟件設(shè)計(jì)模式怠蹂。在它的核心結(jié)構(gòu)中只包含一個(gè)被稱為單例的特殊類善延。通過單例模式可以保證系統(tǒng)中一個(gè)類只有一個(gè)實(shí)例,要實(shí)現(xiàn)這一點(diǎn)城侧,可以從客戶端對其進(jìn)行實(shí)例化開始易遣。因此需要用一種只允許生成對象類的唯一實(shí)例的機(jī)制,“阻止”所有想要生成對象的訪問嫌佑。使用工廠方法來限制實(shí)例化過程豆茫。這個(gè)方法應(yīng)該是靜態(tài)方法(類方法),因?yàn)樽岊惖膶?shí)例去生成另一個(gè)唯一實(shí)例毫無意義歧强。其定義是: 一個(gè)類有且僅有一個(gè)實(shí)例澜薄,并且自行實(shí)例化向整個(gè)系統(tǒng)提供为肮。
在安卓中摊册,我們很多時(shí)候只需要擁有一個(gè)全局對象,這樣有利于我們協(xié)調(diào)整體的行為操作颊艳,比如ImageLoader實(shí)例茅特,在一個(gè)應(yīng)用中只應(yīng)該存在一個(gè)忘分,因?yàn)樵贗mageLoader中又包含了線程池、緩存系統(tǒng)白修、網(wǎng)絡(luò)請求等等妒峦,他們是非常消耗資源的,因此兵睛,沒有理由讓它無限制地實(shí)例化肯骇,這種我們覺得不能隨意地構(gòu)造對象的情況,就是單例模式的應(yīng)用場景了祖很。
實(shí)現(xiàn)單例模式笛丙,有幾個(gè)關(guān)鍵點(diǎn):
- 構(gòu)造函數(shù)不對外開放,一般為private假颇。
- 確保單例類的對象有且只有一個(gè)胚鸯,尤其是在多線程的環(huán)境下。
- 通過一個(gè)靜態(tài)方法或者枚舉返回單例對象笨鸡。
- 確保單例對象在反序列化的時(shí)候不會(huì)重新構(gòu)造對象姜钳。
下面我們就來進(jìn)行一個(gè)簡單的實(shí)例演示。正如我們知道的形耗,在一家公司中通常只有一個(gè)CEO哥桥,CEO對于公司而言是唯一的,此處我們使用CEO為模擬示例激涤。
public class CEO {
private static final CEO mCEO = new CEO();
private CEO() {}
public static CEO getCEO() {
return mCEO;
}
}
非常簡單的代碼泰讽,
CEO
類不能通過new
的形式構(gòu)造對象,還能通過CEO.getCEO()
函數(shù)來獲取昔期,而這個(gè)CEO對象又是靜態(tài)(static)對象并且在聲明的時(shí)候就已經(jīng)初始化了已卸,這就保證了CEO對象的唯一性。此處介紹的是單例模式的一種硼一,叫做餓漢單例模式累澡。
我們繼續(xù)學(xué)習(xí)一下其他的各種單例模式,作者會(huì)盡量解釋幾種實(shí)現(xiàn)單例模式的區(qū)別般贼。
-
懶漢模式#####
懶漢模式是聲明一個(gè)靜態(tài)對象愧哟,并且在用戶第一次調(diào)用
getInstance
時(shí)進(jìn)行初始化,而上面所講述的CEO
類則是在聲明靜態(tài)對象的時(shí)候就已經(jīng)完成了實(shí)例化操作哼蛆。懶漢單例模式的實(shí)現(xiàn)方式如下:.
public class CEO {
private static CEO instance;
private CEO() {}
public static synchronized CEO getInstance() {
if (instance == null) instance = new CEO();
return instance;
}
}
善于發(fā)現(xiàn)的朋友又看到了蕊梧,在此處的
getInstance
方法中有一個(gè)關(guān)鍵字synchronized
,對synchronized
關(guān)鍵字不理解的同學(xué)腮介,請?zhí)D(zhuǎn)到深入理解java中的synchronized關(guān)鍵字肥矢。
關(guān)鍵字synchronized
在此處聲明getInstance
為一個(gè)同步方法,也就是上面所說的在多線程情況下保證單例對象唯一性的手段叠洗。
但是此處存在一個(gè)問題甘改,即使我們的Instance
已經(jīng)被初始化了(第一次調(diào)用時(shí)就會(huì)初始化)旅东,每次調(diào)用getInstance
方法都會(huì)進(jìn)行同步,這樣會(huì)小號不必要的資源十艾,這也是懶漢模式所存在的最大問題抵代。那么,通過上述內(nèi)容忘嫉,我們對懶漢模式做一個(gè)小小的總結(jié)荤牍。
- 優(yōu)點(diǎn): 懶漢單例模式只有在使用時(shí)才會(huì)將類實(shí)例化,在一定程度上節(jié)約了資源庆冕。
- 缺點(diǎn): 第一次加載時(shí)需要進(jìn)行實(shí)例化参淫,反應(yīng)稍慢,最大的問題是每次調(diào)用getInstance方法都需要進(jìn)行同步愧杯,造成不必要的同步開銷涎才。
這種方式一般不推薦使用。
-
Double Check Lock (DLC)實(shí)現(xiàn)單例#####
這里我們直接上示例代碼
public class CEO {
private static CEO sInstance = null;
private CEO() {
}
public static synchronized CEO getInstance() {
if (sInstance == null) {
synchronized (CEO.class) {
if (sInstance == null) {
sInstance = new CEO();
}
}
}
return sInstance;
}
}
我們的關(guān)注點(diǎn)依然在
getInstance
方法上力九,可以看到我們在該方法中對sInstance
做了兩次判斷是否為空:
- 第一層判斷主要為了避免不必要的同步耍铜。
- 第二層為了在null的情況下創(chuàng)建實(shí)例。
估計(jì)此時(shí)會(huì)有部分朋友不知道我在說什么了跌前,沒關(guān)系棕兼,我們一起來進(jìn)行分析:假設(shè)線程A執(zhí)行到
sInstance = new CEO();
這里看到的只是簡單的一行代碼,但實(shí)際上會(huì)進(jìn)行多條匯編指令抵乓,主要做了三件事情:
- 給CEO的實(shí)例分配內(nèi)存伴挚。
- 調(diào)用CEO的構(gòu)造函數(shù),初始化成員字段灾炭。
- 將sInstance對象只想分配的內(nèi)存空間中(完成此步驟時(shí)茎芋,我們的sInstance就不為null了)
以下內(nèi)容可根據(jù)理解能力選擇是否觀看
Java編譯器是允許程序亂序執(zhí)行的,以及JDK1.5之前的
JMM(Java Memory Model蜈出,Java內(nèi)存模型)
中Cache田弥、寄存器到主內(nèi)存回寫順序規(guī)定,上面第二和第三的順序是無法保證的铡原,也就是說偷厦,執(zhí)行順序可能是1-3-2
,也可能是1-2-3
燕刻。如果是前者只泼,并且在3執(zhí)行完畢、2未執(zhí)行之前卵洗,被切換到線程B上请唱,這時(shí)候sInstance
因?yàn)橐呀?jīng)在線程A內(nèi)執(zhí)行過第三點(diǎn),sInstance
已經(jīng)是非空的了,所以籍滴,線程B會(huì)直接取走sInstance
酪夷,然而在實(shí)際上它并沒有走完過程2榴啸,再使用時(shí)就會(huì)報(bào)錯(cuò)孽惰,這就是DCL失效問題。
在JDK1.5以后鸥印,SUN官方調(diào)整了JVM勋功,具體化了volatile
關(guān)鍵字,因此库说,如果是JDK1.5或者之后的版本狂鞋,只需要將sInstance的聲明改為private volatile static CEO sInstance = null
就可以保證sInstance
對象每次都是從主內(nèi)存中讀取的,此時(shí)潜的,使用DCL來完成單例模式一般是不會(huì)出錯(cuò)的骚揍,當(dāng)然,volatile
關(guān)鍵字也會(huì)犧牲一定的程序性能啰挪,但為了程序穩(wěn)定信不,犧牲一丁點(diǎn)性能,是值得的亡呵。
根據(jù)以上所述抽活,DLC模式我們同樣地進(jìn)行適當(dāng)總結(jié):
- 優(yōu)點(diǎn)
- 資源利用率高,第一次執(zhí)行
getInstance
時(shí)單例對象才會(huì)被實(shí)例化锰什,效率高
- 資源利用率高,第一次執(zhí)行
- 缺點(diǎn)
- 第一次加載時(shí)反應(yīng)較慢下硕,也由于Java內(nèi)存模型原因有時(shí)候會(huì)失敗。
- 在高并發(fā)場景下有一定缺陷汁胆。
- DLC模式能夠在需要的時(shí)候才實(shí)例化單例對象梭姓,并且能夠哎絕大多數(shù)場景下保證單例對象的唯一性,除非你的代碼在高并發(fā)場景比較復(fù)雜或者低于JDK1.6的版本下使用嫩码,否則糊昙,這種方式一般能夠滿足需求。
-
靜態(tài)內(nèi)部單例模式#####
DLC模式雖然在一定程度上解決了資源消耗谢谦、多余的同步释牺、線程安全燈問題,但是回挽,它還是在某些情況下出現(xiàn)失效問題没咙。這個(gè)問題被成為雙重檢查鎖定(DLC)失效,在《Java并發(fā)編程與實(shí)踐》一書中千劈,指出這種“優(yōu)化”是丑陋的祭刚,不贊成使用,而建議使用如下代碼替代:
public class CEO {
private CEO() {}
public static synchronized CEO getInstance() {
return CEO_Holder.sInstance;
}
private static class CEO_Holder {
private static final CEO sInstance = new CEO();
}
}
當(dāng)?shù)谝患虞dCEO類的時(shí)候并不會(huì)初始化sInstance,只有在第一次調(diào)用
getInstance
方法時(shí)它才會(huì)被初始化涡驮,因此暗甥,第一次調(diào)用getInstance方法會(huì)導(dǎo)致虛擬機(jī)加載CEO_Holder類,這種方式不僅能夠保證線程安全捉捅,還能夠保證單例對象的唯一性撤防,同時(shí)也延遲了單例的實(shí)例化,所以這是推薦使用的單例模式實(shí)現(xiàn)方式棒口。