寫在前面#
其實我也不知道想說什么,來簡書已經(jīng)足足15天了,每天都在通過文字總結(jié)和分享自己的所學(xué)知識,寫技術(shù)文章好像也已經(jīng)成了生活中的一部分了,但是,更重要的是,我要告訴你們,作為一只異地戀狗在大街上看見別的情侶秀恩愛,真的會超想她,有沒有和我一樣的同胞(掩面失聲痛哭),如果我的心情一片黑暗,我將用學(xué)習(xí)將它照亮!
單例模式####
單例模式(Singleton pattern)是一種常用的軟件設(shè)計模式。在它的核心結(jié)構(gòu)中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統(tǒng)中一個類只有一個實例,要實現(xiàn)這一點,可以從客戶端對其進(jìn)行實例化開始。因此需要用一種只允許生成對象類的唯一實例的機(jī)制,“阻止”所有想要生成對象的訪問。使用工廠方法來限制實例化過程。這個方法應(yīng)該是靜態(tài)方法(類方法),因為讓類的實例去生成另一個唯一實例毫無意義。其定義是: 一個類有且僅有一個實例,并且自行實例化向整個系統(tǒng)提供。
在安卓中,我們很多時候只需要擁有一個全局對象,這樣有利于我們協(xié)調(diào)整體的行為操作,比如ImageLoader實例,在一個應(yīng)用中只應(yīng)該存在一個,因為在ImageLoader中又包含了線程池、緩存系統(tǒng)、網(wǎng)絡(luò)請求等等,他們是非常消耗資源的,因此,沒有理由讓它無限制地實例化,這種我們覺得不能隨意地構(gòu)造對象的情況,就是單例模式的應(yīng)用場景了。
實現(xiàn)單例模式,有幾個關(guān)鍵點:
- 構(gòu)造函數(shù)不對外開放,一般為private。
- 確保單例類的對象有且只有一個,尤其是在多線程的環(huán)境下。
- 通過一個靜態(tài)方法或者枚舉返回單例對象。
- 確保單例對象在反序列化的時候不會重新構(gòu)造對象。
下面我們就來進(jìn)行一個簡單的實例演示。正如我們知道的,在一家公司中通常只有一個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ù)來獲取,而這個CEO對象又是靜態(tài)(static)對象并且在聲明的時候就已經(jīng)初始化了,這就保證了CEO對象的唯一性。此處介紹的是單例模式的一種,叫做餓漢單例模式。
我們繼續(xù)學(xué)習(xí)一下其他的各種單例模式,作者會盡量解釋幾種實現(xiàn)單例模式的區(qū)別。
-
懶漢模式#####
懶漢模式是聲明一個靜態(tài)對象,并且在用戶第一次調(diào)用
getInstance
時進(jìn)行初始化,而上面所講述的CEO
類則是在聲明靜態(tài)對象的時候就已經(jīng)完成了實例化操作。懶漢單例模式的實現(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
方法中有一個關(guān)鍵字synchronized
,對synchronized
關(guān)鍵字不理解的同學(xué),請?zhí)D(zhuǎn)到深入理解java中的synchronized關(guān)鍵字。
關(guān)鍵字synchronized
在此處聲明getInstance
為一個同步方法,也就是上面所說的在多線程情況下保證單例對象唯一性的手段。
但是此處存在一個問題,即使我們的Instance
已經(jīng)被初始化了(第一次調(diào)用時就會初始化),每次調(diào)用getInstance
方法都會進(jìn)行同步,這樣會小號不必要的資源,這也是懶漢模式所存在的最大問題。那么,通過上述內(nèi)容,我們對懶漢模式做一個小小的總結(jié)。
- 優(yōu)點: 懶漢單例模式只有在使用時才會將類實例化,在一定程度上節(jié)約了資源。
- 缺點: 第一次加載時需要進(jìn)行實例化,反應(yīng)稍慢,最大的問題是每次調(diào)用getInstance方法都需要進(jìn)行同步,造成不必要的同步開銷。
這種方式一般不推薦使用。
-
Double Check Lock (DLC)實現(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)注點依然在
getInstance
方法上,可以看到我們在該方法中對sInstance
做了兩次判斷是否為空:
- 第一層判斷主要為了避免不必要的同步。
- 第二層為了在null的情況下創(chuàng)建實例。
估計此時會有部分朋友不知道我在說什么了,沒關(guān)系,我們一起來進(jìn)行分析:假設(shè)線程A執(zhí)行到
sInstance = new CEO();
這里看到的只是簡單的一行代碼,但實際上會進(jìn)行多條匯編指令,主要做了三件事情:
- 給CEO的實例分配內(nèi)存。
- 調(diào)用CEO的構(gòu)造函數(shù),初始化成員字段。
- 將sInstance對象只想分配的內(nèi)存空間中(完成此步驟時,我們的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上,這時候sInstance
因為已經(jīng)在線程A內(nèi)執(zhí)行過第三點,sInstance
已經(jīng)是非空的了,所以,線程B會直接取走sInstance
,然而在實際上它并沒有走完過程2,再使用時就會報錯,這就是DCL失效問題。
在JDK1.5以后,SUN官方調(diào)整了JVM,具體化了volatile
關(guān)鍵字,因此,如果是JDK1.5或者之后的版本,只需要將sInstance的聲明改為private volatile static CEO sInstance = null
就可以保證sInstance
對象每次都是從主內(nèi)存中讀取的,此時,使用DCL來完成單例模式一般是不會出錯的,當(dāng)然,volatile
關(guān)鍵字也會犧牲一定的程序性能,但為了程序穩(wěn)定,犧牲一丁點性能,是值得的。
根據(jù)以上所述,DLC模式我們同樣地進(jìn)行適當(dāng)總結(jié):
- 優(yōu)點
- 資源利用率高,第一次執(zhí)行
getInstance
時單例對象才會被實例化,效率高
- 資源利用率高,第一次執(zhí)行
- 缺點
- 第一次加載時反應(yīng)較慢,也由于Java內(nèi)存模型原因有時候會失敗。
- 在高并發(fā)場景下有一定缺陷。
- DLC模式能夠在需要的時候才實例化單例對象,并且能夠哎絕大多數(shù)場景下保證單例對象的唯一性,除非你的代碼在高并發(fā)場景比較復(fù)雜或者低于JDK1.6的版本下使用,否則,這種方式一般能夠滿足需求。
-
靜態(tài)內(nèi)部單例模式#####
DLC模式雖然在一定程度上解決了資源消耗、多余的同步、線程安全燈問題,但是,它還是在某些情況下出現(xiàn)失效問題。這個問題被成為雙重檢查鎖定(DLC)失效,在《Java并發(fā)編程與實踐》一書中,指出這種“優(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類的時候并不會初始化sInstance,只有在第一次調(diào)用
getInstance
方法時它才會被初始化,因此,第一次調(diào)用getInstance方法會導(dǎo)致虛擬機(jī)加載CEO_Holder類,這種方式不僅能夠保證線程安全,還能夠保證單例對象的唯一性,同時也延遲了單例的實例化,所以這是推薦使用的單例模式實現(xiàn)方式。