設(shè)計模式(1)- 單例模式

前言

單例模式是一種比較簡單也比較常用的模式德澈。這種模式涉及到一個單一的類歇攻,該類負(fù)責(zé)創(chuàng)建自己的實例,同時也確保只有單個實例被創(chuàng)建梆造,并有接口對外提供這個實例缴守。一般單例模式多用于含有多個比較耗費資源的全局變量的工具類中。比如在一個Android應(yīng)用中镇辉,應(yīng)該只有一個ImageLoader實例屡穗,這個ImageLoader中含有線程池、緩存系統(tǒng)忽肛、網(wǎng)絡(luò)請求等村砂,創(chuàng)建多個的話很耗資源,就很沒必要屹逛。下面講幾種比較常見的單例模式:

1.懶漢式

public class SingleTon {
    private static SingleTon instance ;

    private SingleTon(){}

    public static sysnchronized SingleTon getInstance() {
        if (instance == null) {
            instance = new SingleTon() ;
        }
        return instance ;
    }
}

SingleTon 類中先定義了一個靜態(tài)對象础废,并且在第一次調(diào)用 getInstance 時初始化并返回該對象,后面每一次調(diào)用都會做一次判斷罕模。
懶漢式和餓漢式很容易記反有沒有评腺,可能都是褒義詞的“近義詞”吧。之前在看《Thinking In Java》一書中有提到 惰性初始化(Lazy Loading) 一詞淑掌,就是讓對象在將要使用之前的位置初始化蒿讥,避免了內(nèi)存浪費。2個放一起記就好記了,在第一次使用該單例對象的時候初始化的就是懶漢式芋绸。
懶漢式是通過來 synchronized 來加鎖保證線程安全的媒殉,但是鎖會影響效率,而且絕大多數(shù)情況下是不需要同步的侥钳。

2.餓漢式

public class SingleTon {
    private static final SingleTon instance = new SingleTon() ;

    private SingleTon(){}

    public static sysnchronized SingleTon getInstance() {
        return instance ;
    }
}

這種方式基于 ClassLoader 避免了多線程同步的問題适袜,沒有加鎖,執(zhí)行效率會提高舷夺。但是instance 在類裝載的時候就實例化苦酱,浪費內(nèi)存,很容易產(chǎn)生垃圾對象给猾。關(guān)于類裝載看一下這篇文章:單例模式之類的加載 疫萤。

3.雙重檢查鎖(DLC, Double Check Lock)

public class SingleTon {
    private static final SingleTon instance = null ;

    private SingleTon(){}

    public static sysnchronized SingleTon getInstance() {
        if(instance == null) {
            sysnchronized(SingleTon.class) {
                if(instance == null) {
                    instance = new SingleTon() ;
                }
            }
        }
        return instance ;
    }
}

我的 synchronized / Lock+Volatile 這篇文章有關(guān)于java中3種鎖的介紹敢伸,感興趣的請移步看一下扯饶。
關(guān)于這里為什么要用到2個 if(instance == null)來判斷,是因為 instance = new SingleTon() 這一句不是一個原子操作池颈,用偽代碼來表示分為三步:

inst = allocat() ;   // 分配內(nèi)存 
constructor(inst) ;  // 執(zhí)行構(gòu)造函數(shù)
instance = inst ;    // 賦值

Java編譯器允許處理器亂序執(zhí)行尾序,也就是可能會有指令沖排序發(fā)生,也就是說上面的第2躯砰、3步執(zhí)行的順序是無法保證的每币。也就是說執(zhí)行順序可能是1-2-3也可能是1-3-2.如果是1-3-2的話,當(dāng)線程A執(zhí)行了1-3時琢歇,instance已經(jīng)不為 null 了兰怠,但是還沒有調(diào)用構(gòu)造函數(shù)初始化,這時候切換到線程B李茫,B就直接取走了instance揭保,在使用的時候就會出錯了。當(dāng)然 volatile 關(guān)鍵字也能保證原子操作不亂序執(zhí)行魄宏,我上面的文章中有說明秸侣。

4.登記式(靜態(tài)內(nèi)部類)

public class SingleTon {

    private SingleTon(){}

    public static sysnchronized SingleTon getInstance() {
        return SingleTonHolder.instance ;
    }

    private static class SingleTonHolder {
        private static final SingleTon instance = new SingleTon() ;
    }
}

這種方式能達到雙檢鎖方式一樣的功效,但實現(xiàn)更簡單宠互。對靜態(tài)域使用惰性初始化塔次,應(yīng)使用這種方式而不是雙檢鎖方式。這種方式只適用于靜態(tài)域的情況名秀,雙檢鎖方式可在實例域需要延遲初始化時使用励负。
這種方式同樣利用了 classloder 機制來保證初始化 instance 時只有一個線程,它跟第 DCL 種方式不同的是:第 DCL 種方式只要 Singleton 類被裝載了匕得,那么 instance 就會被實例化(沒有達到 lazy loading 效果)继榆,而這種方式是 Singleton 類被裝載了巾表,instance 不一定被初始化。因為 SingletonHolder 類沒有被主動使用略吨,只有顯示通過調(diào)用 getInstance 方法時集币,才會顯示裝載 SingletonHolder 類,從而實例化 instance翠忠。想象一下鞠苟,如果實例化 instance 很消耗資源,所以想讓它延遲加載秽之,另外一方面当娱,又不希望在 Singleton 類加載時就實例化,因為不能確保 Singleton 類還可能在其他的地方被主動使用從而被加載考榨,那么這個時候?qū)嵗?instance 顯然是不合適的跨细。這個時候,這種方式相比第 DCL 種方式就顯得很合理河质。

5.枚舉單例

public class SingleTon {
    INSTANCE ;

    public void whateverMethod(){ }
}

在上述的4中單例實現(xiàn)中冀惭,在反序列化的時候都可能會創(chuàng)建一個新的實例。當(dāng)然也可以通過鉤子函數(shù) readResolve() 來阻止反序列化的時候重新生成對象掀鹅。但是枚舉最大的有點就是簡單散休,自動支持序列化機制,而且能保證絕對的單例乐尊。但是枚舉是在Java1.5之后才加入的新特性戚丸。

總結(jié):

不管以哪種形式實現(xiàn)單例模式,核心原理都是 將構(gòu)造函數(shù)私有化科吭,并且通過靜態(tài)方法來獲取一個唯一的實例,在獲取的過程中必須保證線程安全猴鲫、防止反序列化重新生成實例对人。一般情況下,不建議使用懶漢方式拂共,建議使用餓漢方式牺弄。只有在要明確實現(xiàn) lazy loading 效果時,才會使用登記式宜狐。如果涉及到反序列化創(chuàng)建對象時势告,可以使用枚舉方式。

參考書籍:

《Android源碼設(shè)計模式解析與實戰(zhàn)》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末抚恒,一起剝皮案震驚了整個濱河市咱台,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌俭驮,老刑警劉巖回溺,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件春贸,死亡現(xiàn)場離奇詭異,居然都是意外死亡遗遵,警方通過查閱死者的電腦和手機萍恕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來车要,“玉大人允粤,你說我怎么就攤上這事∫硭辏” “怎么了类垫?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長登澜。 經(jīng)常有香客問我阔挠,道長,這世上最難降的妖魔是什么脑蠕? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任购撼,我火速辦了婚禮,結(jié)果婚禮上谴仙,老公的妹妹穿的比我還像新娘迂求。我一直安慰自己,他們只是感情好晃跺,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布揩局。 她就那樣靜靜地躺著,像睡著了一般掀虎。 火紅的嫁衣襯著肌膚如雪凌盯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天烹玉,我揣著相機與錄音驰怎,去河邊找鬼。 笑死二打,一個胖子當(dāng)著我的面吹牛县忌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播继效,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼症杏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瑞信?” 一聲冷哼從身側(cè)響起厉颤,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凡简,沒想到半個月后走芋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绩郎,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年翁逞,在試婚紗的時候發(fā)現(xiàn)自己被綠了肋杖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡挖函,死狀恐怖状植,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怨喘,我是刑警寧澤津畸,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站必怜,受9級特大地震影響肉拓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜梳庆,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一暖途、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膏执,春花似錦驻售、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至征峦,卻和暖如春迟几,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背栏笆。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工类腮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人竖伯。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓存哲,卻偏偏與公主長得像因宇,于是被迫代替她去往敵國和親七婴。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容