概念
java中單例模式是一種常見的設(shè)計模式,單例模式的寫法有好幾種级乍,比較常見的有:懶漢式單例、餓漢式單例帚湘。
單例模式有以下特點(diǎn):
1玫荣、單例類只能有一個實例。
2大诸、單例類必須自己創(chuàng)建自己的唯一實例崇决。
3、單例類必須給所有其他對象提供這一實例底挫。
單例模式可以確保某個類只有一個實例恒傻,而且自行實例化并向整個系統(tǒng)提供這個實例。
想必有很多人和我一樣建邓,最早接觸到的設(shè)計模式就是單例模式盈厘。和其他設(shè)計模式相比較,單例模式確實顯得簡單一些官边,而且它也是Android開發(fā)當(dāng)中最常用的設(shè)計模式之一沸手,從許多開源框架源碼中都能看見單例的應(yīng)用外遇。廢話不多說,直接上代碼契吉。
一跳仿、首先看看單例模式在幾個著名框架內(nèi)的使用情況:
1、EventBus
/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
2捐晶、Android-Universal-Image-Loader
/** Returns singleton class instance */
public static ImageLoader getInstance() {
if (instance == null) {
synchronized (ImageLoader.class) {
if (instance == null) {
instance = new ImageLoader();
}
}
}
return instance;
}
從上面的代碼中可以看到菲语,大神們造輪子比較青睞的寫法還是雙重校驗鎖。盡管受到大神們青睞惑灵,但這并不意味著這種寫法是完全沒有問題的山上。
二、接下來我們分析一下單例模式的各種寫法:
懶漢式
//懶漢式單例類.在第一次調(diào)用的時候?qū)嵗约?
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//靜態(tài)工廠方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
但是以上懶漢式單例的實現(xiàn)沒有考慮線程安全問題英支,它是線程不安全的佩憾,并發(fā)環(huán)境下很可能出現(xiàn)多個Singleton實例,要實現(xiàn)線程安全干花,有以下兩種方式妄帘,都是對getInstance這個方法改造,保證了懶漢式單例的線程安全池凄。
1寄摆、給方法添加同步synchronized
private static Singleton single = null;
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
同步方法不可避免地會對性能造成一定的影響,因此使用時要慎重考慮修赞。
2婶恼、雙重校驗鎖
private static volatile Singleton singleton = null;
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
盡管雙重檢查鎖定背后的理論是完美的。不幸地是柏副,現(xiàn)實完全不同勾邦。雙重檢查鎖定的問題是:并不能保證它會在單處理器或多處理器計算機(jī)上順利運(yùn)行。因此我們通過給singleton加上volatile 關(guān)鍵字來應(yīng)對性能敏感的場合割择。
檢查鎖定失敗的問題并不歸咎于 JVM 中的實現(xiàn) bug眷篇,而是歸咎于 Java 平臺內(nèi)存模型。內(nèi)存模型允許所謂的“無序?qū)懭搿崩笥荆@也是這些習(xí)語失敗的一個主要原因蕉饼。
想了解具體原因請參考 Java單例模式中雙重檢查鎖的問題
3、靜態(tài)內(nèi)部類(Holder模式)
我們既希望利用餓漢模式中靜態(tài)變量的方便和線程安全玛歌;又希望通過懶加載規(guī)避資源浪費(fèi)昧港。Holder模式滿足了這兩點(diǎn)要求:核心仍然是靜態(tài)變量,足夠方便和線程安全支子;通過靜態(tài)的Holder類持有真正實例创肥,間接實現(xiàn)了懶加載。
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
相對于餓漢模式,Holder模式僅增加了一個靜態(tài)內(nèi)部類的成本叹侄,與飽漢的變種3效果相當(dāng)(略優(yōu)),都是比較受歡迎的實現(xiàn)方式。同樣建議考慮禽捆。
餓漢式
//餓漢式單例類.在類初始化時得湘,已經(jīng)自行實例化
public class Singleton1 {
private Singleton1() {}
private static final Singleton1 single = new Singleton1();
//靜態(tài)工廠方法
public static Singleton1 getInstance() {
return single;
}
}
餓漢式在類創(chuàng)建的同時就已經(jīng)創(chuàng)建好一個靜態(tài)的對象供系統(tǒng)使用(static關(guān)鍵字的功勞)淘正,并且巧妙地利用final關(guān)鍵字保證以后不再改變囤采,所以天生是線程安全的蕉毯。
Android Studio可以直接新建單例模式類代虾,使用的就是餓漢式寫法棉磨,我們可以看看。
代碼如下:
/**
* Created by QianWei on 2017/11/23.
*/
public class Singleton1 {
private static Singleton1 ourInstance = new Singleton1();
public static Singleton1 getInstance() {
return ourInstance;
}
private Singleton1() {
}
}
餓漢式和懶漢式區(qū)別
從名字上來說,餓漢和懶漢,餓漢就是類一旦加載蓄喇,就把單例初始化完成妆偏,保證getInstance的時候叔锐,單例是已經(jīng)存在的了,而懶漢比較懶解取,只有當(dāng)調(diào)用getInstance的時候蔓肯,才回去初始化這個單例蔗包。
另外從以下兩點(diǎn)再區(qū)分以下這兩種方式:
1、線程安全:
餓漢式天生就是線程安全的误澳,可以直接用于多線程而不會出現(xiàn)問題脓匿,懶漢式本身是非線程安全的米母,為了實現(xiàn)線程安全有幾種寫法铁瞒,分別是上面的1慧耍、2芍碧、3泌豆,這三種實現(xiàn)在資源加載和性能方面有些區(qū)別蔬浙。
2畴博、資源加載和性能:
餓漢式在類創(chuàng)建的同時就實例化一個靜態(tài)對象出來,不管之后會不會使用這個單例庶艾,都會占據(jù)一定的內(nèi)存颖榜,但是相應(yīng)的且蓬,在第一次調(diào)用時速度也會更快,因為其資源已經(jīng)初始化完成恶阴,
而懶漢式顧名思義诈胜,會延遲加載,在第一次使用該單例的時候才會實例化對象出來冯事,第一次調(diào)用時要做初始化焦匈,如果要做的工作比較多,性能上會有些延遲昵仅,之后就和餓漢式一樣了缓熟。
至于1、2、3這三種實現(xiàn)又有些區(qū)別够滑,
第1種垦写,在方法調(diào)用上加了同步,雖然線程安全了彰触,但是每次都要同步梯澜,會影響性能,畢竟99%的情況下是不需要同步的渴析,
第2種晚伙,在getInstance中做了兩次null檢查,確保了只有第一次調(diào)用單例的時候才會做同步俭茧,這樣也是線程安全的咆疗,同時避免了每次都同步的性能損耗
第3種,利用了classloader的機(jī)制來保證初始化instance時只有一個線程母债,所以也是線程安全的午磁,同時沒有性能損耗,所以一般我傾向于使用這一種毡们。
三迅皇、總結(jié)
上面的分析都忽略了反射和序列化的問題。通過反射或序列化衙熔,我們?nèi)匀荒軌蛟L問到私有構(gòu)造器登颓,創(chuàng)建新的實例破壞單例模式。此時红氯,只有枚舉模式能天然防范這一問題框咙。
參考鏈接
JAVA設(shè)計模式之單例模式
程序猿應(yīng)該記住的幾條基本規(guī)則
面試中單例模式有幾種寫法