1.什么是單例模式?
Singleton的英文意思是單獨,也就是一個人成黄,應(yīng)用在面向?qū)ο笳Z言上,通常翻譯成「單例」逻杖,單一的實例「Instance」奋岁。Singleton模式可以保證一個類僅有一個實例,并提供一個訪問這個實例的方法弧腥。
Java源碼中就擁有許多的設(shè)計模式厦取,其中Java.lang.Runtime類就實現(xiàn)了單例模式潮太,每個Java程序運行時管搪,都有唯一一個Runtime實例,透過靜態(tài)方法getRuntime()铡买,獲得實例更鲁,例如:
Runtime runtime = Runtime.getRuntime();
Java.lang.Runtime類的部分源碼:
2.單例模式解決什么需求?
當(dāng)一個類被設(shè)計來管理共享資源的時候奇钞,我們僅需實例化一個對象澡为,比方說:線程池、緩存景埃、日志對象等等媒至。如果new出多個實例,那么容易導(dǎo)致一些問題產(chǎn)生谷徙,如資源使用過量拒啰,程序異常,結(jié)果不一致等等完慧。
舉個栗子:當(dāng)在一個Web應(yīng)用中連接數(shù)據(jù)庫的Connection對象谋旦,如果每次訪問都new一個實例,那么當(dāng)發(fā)生成千上萬甚至更多的訪問在短時間內(nèi)并發(fā)屈尼,這將導(dǎo)致服務(wù)器資源的大量開銷册着,因為對象不能被垃圾收集器及時的回收。但是脾歧,如果服務(wù)器對此僅實例化一次甲捏,每次Connection對象被使用后放回線程池,其他訪問連接時繼續(xù)使用它鞭执,便使得服務(wù)器性能大大的提升摊鸡,這就是單例模式的實踐绽媒。
3.單例模式的實現(xiàn):
VERSION 1:懶漢式之幼年期
public class Singleton {
private static Singleton singleton = null;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
上面這段代碼中,關(guān)于Singletom模式的幾個特點:
1.私有(private)的構(gòu)造函數(shù)免猾,表明這個類是不可能從外部形成實例了是辕。
2.即然這個類是不可能形成實例,那么猎提,我們需要一個靜態(tài)的方式讓其形成實例:getInstance()获三。
3.在getInstance()中,先做判斷是否已形成實例锨苏,如果已形成則直接返回實例疙教,否則創(chuàng)建實例。
4.所形成的實例保存在自己類中的私有成員中伞租。
5.我們?nèi)嵗龝r贞谓,僅需要使用Singleton.getInstance()就行了。
上面這段代碼是有缺陷的葵诈,僅適合單線程情況裸弦。在多線程情況下,所有的全局共享的東西都會變得非常的危險作喘,上述代碼也一樣理疙,在多線程情況下,如果多個線程同時調(diào)用getInstance()的話泞坦,那么窖贤,可能會有多個進程同時通過 (singleton== null)的條件檢查,于是贰锁,多個實例就創(chuàng)建出來赃梧,并且很可能造成內(nèi)存泄露問題。
VERSION 2:懶漢式之成長期
public class Singleton {
private static Singleton singleton = null;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
OK豌熄,既然可能產(chǎn)生多個線程通過 (singleton== null)的條件檢查授嘀,那么我們將在VERSION 2中使用線程互斥或同步來避免產(chǎn)生多個實例,代碼如上房轿。
雖然使用synchronized方法會幫助我們同步所有線程粤攒,讓并行的線程變成串行,一個一個地去new囱持,但最終結(jié)果與VERSION 1無異夯接,還是會出現(xiàn)很多實例。所以接下來需要將(singleton == null)條件也同步起來纷妆。
VERSION 3:懶漢式之成熟期
public class Singleton {
private static Singleton singleton = null;
private Singleton() {}
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
}
現(xiàn)在的代碼已經(jīng)不會出現(xiàn)多線程new出多個實例的情況了盔几,可是稍有不足的是,所有調(diào)用getInstance()方法的線程都得同步掩幢。正常情況下逊拍,創(chuàng)建對象的動作僅有一次上鞠,后面的動作均是讀取對象。如果每次讀取對象需要經(jīng)過線程同步芯丧,將會影響程序的性能芍阎,所以接下來更好方法是:在線程同步實例化對象之前,先進行(siglenton == null)的判斷缨恒,如果不為空谴咸,則直接讀取對象。
VERSION 4:懶漢式之完全體
public class Siglenton {
private static Siglenton siglenton = null;
private Siglenton() {}
public static Siglenton getInstance() {
if (siglenton == null) {
synchronized (Siglenton.class) {
if (siglenton == null) {
siglenton = new Siglenton();
}
}
}
return siglenton;
}
}
好的骗露,這個版本稱為「雙重檢查Double-Check」岭佳。下面是說明:
1.第一個條件是說,如果實例創(chuàng)建了萧锉,那就不需要同步了珊随,直接返回實例。
2.不然柿隙,我們就開始同步線程叶洞。
3.第二個條件是說,如果被同步的線程中优俘,有一個線程創(chuàng)建了對象京办,那么別的線程無需再創(chuàng)建了掀序。
這是稍微不錯的單例模式帆焕,可是在一些極端的情況下還是會出問題,引用陳皓大叔說的:
無論你的代碼寫得有多好不恭,其只能在特定的范圍內(nèi)工作叶雹,超出這個范圍就要出Bug。
OK换吧,那么最后折晦,我們就不繼續(xù)糾纏懶漢式的單例模式了。
關(guān)于單例模式的實現(xiàn)方式還有很多種沾瓦,比如接下來展示的:餓漢式满着、靜態(tài)內(nèi)部類,枚舉等等贯莺。
餓漢式:
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
靜態(tài)內(nèi)部類:
public class Singleton {
private static class SingletonHolder {
private static final Singleton singleton = new Singleton();
}
private Singleton() {}
public static final Singleton getInstance() {
return SingletonHolder.sigleton;
}
}
枚舉:
public enum Singleton {
SINGLETON;
public void whateverMethod() {
}
}
參考:
「1」:Double-checked locking: Clever, but broken
「2」:Difference between static class and singleton pattern?
「3」:深入淺出單實例Singleton設(shè)計模式
「4」:單例模式的七種寫法