設(shè)計(jì)模式之單例模式詳解

設(shè)計(jì)模式之單例模式詳解

單例模式寫(xiě)法大全,也許有你不知道的寫(xiě)法

導(dǎo)航

  • 引言
  • 什么是單例?
  • 單例模式作用
  • 單例模式的實(shí)現(xiàn)方法

引言

單例模式想必是大家接觸的比較多的一種模式了,就算沒(méi)用過(guò)但是肯定聽(tīng)過(guò)他的鼎鼎大名了。在我初入編程界時(shí)聽(tīng)到最多的就是單例模式侵续,工廠模式,觀察者模式了憨闰。特別是觀察者模式在Android開(kāi)發(fā)中幾乎是隨處可見(jiàn)状蜗,不過(guò)今天我們先來(lái)學(xué)習(xí)一個(gè)看似簡(jiǎn)單很多的單例模式。

什么是單例模式鹉动?

單例模式確保某一個(gè)類(lèi)只有一個(gè)實(shí)例轧坎。

單例模式有什么用?

為什么要確保一個(gè)類(lèi)只有一個(gè)實(shí)例训裆?有什么時(shí)候才需要用到單例模式呢眶根?聽(tīng)起來(lái)一個(gè)類(lèi)只有一個(gè)實(shí)例好像沒(méi)什么用呢蜀铲!
那我們來(lái)舉個(gè)例子。比如我們的APP中有一個(gè)類(lèi)用來(lái)保存運(yùn)行時(shí)全局的一些狀態(tài)信息属百,如果這個(gè)類(lèi)實(shí)現(xiàn)不是單例的记劝,那么App里面的組件能夠隨意的生成多個(gè)類(lèi)用來(lái)保存自己的狀態(tài),等于大家各玩各的族扰,那這個(gè)全局的狀態(tài)信息就成了笑話(huà)了厌丑。而如果把這個(gè)類(lèi)實(shí)現(xiàn)成單例的,那么不管App的哪個(gè)組件獲取到的都是同一個(gè)對(duì)象(比如Application類(lèi)渔呵,除了多進(jìn)程的情況下)怒竿。

怎么實(shí)現(xiàn)單例模式?

單例模式的定義和功能都是比較簡(jiǎn)單清楚的東西扩氢,那么到底怎么實(shí)現(xiàn)這個(gè)模式呢耕驰?

1.餓漢式

可能有的小伙伴們會(huì)想到利用Java的靜態(tài)域初始化機(jī)制來(lái)實(shí)現(xiàn)

public class SimpleSingleton {

    private static SimpleSingleton instance=new SimpleSingleton();

    /**
     * 構(gòu)造方法私有化,幾乎所有的單例模式實(shí)現(xiàn)都會(huì)將構(gòu)造方法私有化
     */
    private SimpleSingleton() {
    }

    public static SimpleSingleton getInstance(){
        return instance;
    }
}

??這種寫(xiě)法很簡(jiǎn)單录豺,先把構(gòu)造函數(shù)設(shè)為private的(幾乎大部分的單例寫(xiě)法都會(huì)這么做)朦肘。然后在類(lèi)中設(shè)置一個(gè)靜態(tài)的字段,并調(diào)用構(gòu)造函數(shù)双饥。這樣jvm在加載這個(gè)類(lèi)的時(shí)候就會(huì)自動(dòng)初始化這個(gè)類(lèi)媒抠,接著每次需要使用的時(shí)候都調(diào)用getInstance方法獲得類(lèi)實(shí)例。而java的語(yǔ)法規(guī)則保證new SimpleSingleton()自會(huì)調(diào)用一次咏花。

??使用這種方式實(shí)現(xiàn)的單例是線(xiàn)程安全的(這一點(diǎn)是由jvm保證的)趴生,并且在類(lèi)加載的時(shí)候就已經(jīng)生成了一個(gè)實(shí)例,當(dāng)調(diào)用的時(shí)候get獲取這個(gè)實(shí)例是就非郴韬玻快苍匆。

凡事都有優(yōu)缺點(diǎn),餓漢式單例也不例外矩父。他的一個(gè)很明顯的缺點(diǎn)就是在性能上锉桑。jvm會(huì)在加載類(lèi)的時(shí)候直接初始化實(shí)例,而如果這個(gè)類(lèi)的實(shí)例在應(yīng)用中使用頻率并不高窍株,有的時(shí)候整個(gè)App從被用戶(hù)打開(kāi)到結(jié)束都不會(huì)使用一次這個(gè)類(lèi)實(shí)例,那么這個(gè)初始化的操作就是完全浪費(fèi)了攻柠。

為了解決這個(gè)問(wèn)題球订,我們想了一種新的實(shí)現(xiàn)方式。

2.懶漢式

public class ServiceNotThreadSafe {

    public static ServiceNotThreadSafe INSTANCE = null;

    /**
     * 必備操作
     */
    private ServiceNotThreadSafe() {
    }

    /**
     * @return instance
     */
    public static ServiceNotThreadSafe getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new ServiceNotThreadSafe();
        }
        return INSTANCE;
    }
}

懶漢式的寫(xiě)法有一個(gè)懶加載的效果瑰钮,只有當(dāng)?shù)谝淮握{(diào)用getInstance方法時(shí)才會(huì)去實(shí)例化一個(gè)對(duì)象冒滩。可能細(xì)心的小伙伴已經(jīng)注意到這個(gè)很直白的類(lèi)名了NotThreadSafe浪谴。沒(méi)錯(cuò)這種寫(xiě)法在單線(xiàn)程中沒(méi)有任何問(wèn)題开睡,但是在并發(fā)程序中就無(wú)法保證 類(lèi)實(shí)例只有一個(gè)的情況了因苹。

為了解決上面的問(wèn)題,我們相出了一個(gè)新的寫(xiě)法

3.懶漢式 —— 單鎖定法

public class ServiceThreadSafe {

    public static ServiceThreadSafe instance;

    /**
     * 還是常規(guī)操作的私有構(gòu)造函數(shù)
     */
    private ServiceThreadSafe() {
    }

    public static synchronized ServiceThreadSafe getInstance(){
        if (instance==null){
            instance=new ServiceThreadSafe();
        }
        return instance;
    }
}

這種寫(xiě)法沒(méi)什么好說(shuō)的篇恒,只是在getInstance方法上加了一個(gè)內(nèi)置同步鎖扶檐,從而保證了線(xiàn)程安全。但是也因此引入了一個(gè)新的問(wèn)題 —— 同步鎖范圍太大胁艰,影響并發(fā)性能(在getInstance方法并不是頻繁調(diào)用下問(wèn)題不大)款筑。

為了解決這個(gè)問(wèn)題,聰明的程序員們想到了另一種寫(xiě)法腾么。

4.雙重鎖定法

public class ServiceDoubleCheck {

    /**
     * 注意這里加了 volatile 修飾符奈梳,用來(lái)保證內(nèi)存可見(jiàn)性(限制指令重排序)。
     * 具體各位小伙伴可以Google一下解虱。
     * 如果你沒(méi)加這個(gè)修飾符的話(huà)攘须,那么具體結(jié)果只能看編譯器,jvm和cpu的心情了O(∩_∩)O~~
     */
    public static volatile ServiceDoubleCheck instance = null;

    /**
     * 大家都懂得操作
     */
    private ServiceDoubleCheck() {
    }

    /**
     * 理論上只要第一次的時(shí)候才會(huì)完全走完整個(gè)方法殴泰,之后進(jìn)入這個(gè)方法時(shí)instance==null都不成立
     * 而不用在進(jìn)入內(nèi)部的同步代碼塊阻课,帶來(lái)新能上的優(yōu)勢(shì)
     * @return
     */
    public static ServiceDoubleCheck getInstance() {
        if (instance == null) {
            synchronized (ServiceDoubleCheck.class) {
                if (instance == null) {
                    instance = new ServiceDoubleCheck();
                }
            }
        }
        return instance;
    }
}

優(yōu)點(diǎn):

  • 資源利用率高,懶加載的形式艰匙,不使用就不會(huì)實(shí)例化
  • 線(xiàn)程安全
    缺點(diǎn):
  • 寫(xiě)法略微繁瑣
  • 第一次加載時(shí)速度不快

5.懶漢式靜態(tài)內(nèi)部類(lèi)寫(xiě)法

public class ServiceInner {

    /**
     * 實(shí)現(xiàn)一個(gè)靜態(tài)內(nèi)部類(lèi)
     */
    private static class Instance{
        private static ServiceInner instance=new ServiceInner();
    }
    
    public static ServiceInner getInstance(){
        return Instance.instance;
    }

    private ServiceInner() {
    }

}

優(yōu)點(diǎn):

  • 線(xiàn)程安全
  • 懶加載形式限煞,資源利用率高
    缺點(diǎn):
  • 第一次加載速度不快

6.枚舉實(shí)現(xiàn) —— 一個(gè)《effective java》 作者都推薦的方法

public class Resources {

    public enum ResourcesInstance {
        INSTANCE;

        private Resources instance;

        ResourcesInstance() {
            this.instance = new Resources();
        }

        public Resources getInstance() {
            return instance;
        }
    }
}

之前介紹過(guò)的哪些單例實(shí)現(xiàn)都有一個(gè)問(wèn)題,就是不能保證序列化生成另一個(gè)實(shí)例员凝。比如先序列化寫(xiě)入到文件署驻,然后再?gòu)奈募x取反序列化回來(lái),這樣子我們就會(huì)得到兩個(gè)實(shí)例健霹,這就違背了單例的原則旺上。而用枚舉實(shí)現(xiàn)能解決這個(gè)問(wèn)題。

總結(jié)

基本上單例的實(shí)現(xiàn)方法都介紹完了糖埋,一般實(shí)際應(yīng)用中如果對(duì)象的實(shí)例化并不是很耗費(fèi)資源的話(huà)使用最簡(jiǎn)單的餓漢法就行了宣吱。如果需要對(duì)象懶加載則可以選用雙重鎖定法(如果不需要考慮線(xiàn)程安全的話(huà)可以使用簡(jiǎn)單的懶漢式)。而要在序列化的過(guò)程中保證單例的話(huà)就要使用枚舉的方法來(lái)實(shí)現(xiàn)了瞳别。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末征候,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子祟敛,更是在濱河造成了極大的恐慌疤坝,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馆铁,死亡現(xiàn)場(chǎng)離奇詭異跑揉,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)历谍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)现拒,“玉大人,你說(shuō)我怎么就攤上這事望侈∮∈撸” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵甜无,是天一觀的道長(zhǎng)扛点。 經(jīng)常有香客問(wèn)我,道長(zhǎng)岂丘,這世上最難降的妖魔是什么陵究? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮奥帘,結(jié)果婚禮上铜邮,老公的妹妹穿的比我還像新娘。我一直安慰自己寨蹋,他們只是感情好松蒜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著已旧,像睡著了一般秸苗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上运褪,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天惊楼,我揣著相機(jī)與錄音,去河邊找鬼秸讹。 笑死檀咙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的璃诀。 我是一名探鬼主播弧可,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼劣欢!你這毒婦竟也來(lái)了棕诵?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤氧秘,失蹤者是張志新(化名)和其女友劉穎年鸳,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體丸相,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年彼棍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灭忠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膳算。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖弛作,靈堂內(nèi)的尸體忽然破棺而出涕蜂,到底是詐尸還是另有隱情,我是刑警寧澤映琳,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布机隙,位于F島的核電站,受9級(jí)特大地震影響萨西,放射性物質(zhì)發(fā)生泄漏有鹿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一谎脯、第九天 我趴在偏房一處隱蔽的房頂上張望葱跋。 院中可真熱鬧,春花似錦源梭、人聲如沸娱俺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)荠卷。三九已至,卻和暖如春烛愧,著一層夾襖步出監(jiān)牢的瞬間油宜,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工屑彻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留验庙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓社牲,卻偏偏與公主長(zhǎng)得像粪薛,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子搏恤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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