設(shè)計(jì)模式整理(2) 單例模式

學(xué)習(xí)《Android 源碼設(shè)計(jì)模式解析與實(shí)踐》系列筆記

什么是單例

單例模式是應(yīng)用最廣残腌,也是最容易理解的模式之一村斟。
在它的核心結(jié)構(gòu)中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統(tǒng)中抛猫,應(yīng)用該模式的一個類只有一個實(shí)例蟆盹。即一個類只有一個對象實(shí)例。

定義

《設(shè)計(jì)模式》(艾迪生維斯理, 1994)中的定義:“保證一個類僅有一個實(shí)例闺金,并提供一個訪問它的全局訪問點(diǎn)逾滥。”
Java 中單例模式定義:“一個類有且僅有一個實(shí)例败匹,并且自行實(shí)例化向整個系統(tǒng)提供寨昙。”

使用場景

為了避免某個類創(chuàng)建多個對象而造成資源的消耗掀亩,或者是這個類型的對象應(yīng)該且只有一個舔哪。

結(jié)構(gòu)

單例模式 UML 圖

單例模式要點(diǎn):

  1. 構(gòu)造行數(shù)不對外開發(fā),需要設(shè)置為 privte;
  2. 單例類自行實(shí)例化一個對象槽棍;
  3. 外部能通過一個靜態(tài)方法或者是枚舉拿到該單例類對象尸红;
  4. 確保單例類對象唯一。

實(shí)現(xiàn)

單例的實(shí)現(xiàn)有多重方式:

1. 餓漢模式

public class Singleton {
    private static final Singleton sInstance = new Singleton();
    private Singleton() {}
    public static Singleton getsInstance() {
        return sInstance;
    }
}

餓漢模式因?yàn)槭琴x值的靜態(tài)變量,所以會在類加載的時候就進(jìn)行了初始化外里,也就是這時已經(jīng)創(chuàng)建好了靜態(tài)的單例對象怎爵。
這個對象是唯一的,也是線程安全的盅蝗。

缺點(diǎn):
過早初始化鳖链,占用資源。

2. 懶漢模式

public class Singleton {
    private static Singleton sInstance = null;
    private Singleton() {}
    public static synchronized Singleton getsInstance() {
        if (sInstance == null) {
            sInstance = new Singleton();
        }
        return sInstance;
    }
}

懶漢模式在會獲取實(shí)例的時候才進(jìn)行初始化墩莫,較餓漢模式節(jié)約資源芙委。但是為了保證多線程下第一次調(diào)用時實(shí)例化對象的唯一性,加了 synchronized 關(guān)鍵字狂秦,這個在初始化后的同步都是沒有必要的灌侣,因此會造成不必要的同步開銷。

3. 雙重校驗(yàn)鎖(double check lock)模式

public class Singleton {
    private static Singleton sInstance = null;
    private Singleton() {}
    public static Singleton getsInstance() {
        if (sInstance == null) {
            synchronized (Singleton.class) {
                if (sInstance == null) {
                    sInstance = new Singleton();
                }
            }
        }
        return sInstance;
    }
}

可以看到裂问,雙重校驗(yàn)鎖模式是在 2 懶漢模式的基礎(chǔ)上的優(yōu)化侧啼。
同步鎖不再加在外層函數(shù)上,而是加在了初始化模塊上堪簿。這樣既保證了線程安全問題痊乾,又解決了初始化后同步消耗問題。


這種模式的關(guān)鍵點(diǎn)是在兩個判空上面椭更,第一次的判空是為了避免不必要的同步哪审,而第二步是為了確保不被多次實(shí)例化。
例如:A 線程和 B 線程同時執(zhí)行到 synchronized (Singleton.class) 這一步虑瀑,A 拿到了鎖湿滓,(B 則會等待 A 執(zhí)行完后釋鎖)然后執(zhí)行了 sInstance = new Singleton()。這時舌狗,A 執(zhí)行完了茉稠,并且實(shí)例化了 sInstance 了,然后釋放鎖后 B 拿到了鎖把夸,B 繼續(xù)往下執(zhí)行而线,假設(shè)這時沒有第二次判空,那 B 會再次實(shí)例化一個 sInstance 對象恋日,這樣 A 和 B 拿到的對象就不是同一個了膀篮。所以說第二次的判空是必須的,這時用來保證實(shí)例化對象的唯一性的岂膳。


然而誓竿,兩次判空就真的能保證實(shí)例化對象的唯一性了嗎?
答案是否定的谈截。
在 A 執(zhí)行到 Instance = new Singleton()時筷屡,看似是一句代碼涧偷,但實(shí)際上它不是一個原子操作,這句代碼最終會被編譯成多條匯編指令(真實(shí)淡疼)毙死,匯編指令大概做了下面三個操作:
(1) 給 Singeton 的實(shí)例分配內(nèi)存燎潮;
(2) 調(diào)用 Singleton() 的構(gòu)造函數(shù),初始化成員字段扼倘;
(3) 將 sInstance 對象指先分配的 內(nèi)存空間(此時 sInstance 就不是 null 了
)确封。

可以看到,如果是順序執(zhí)行的再菊,也是沒有問題的爪喘,問題是在 JVM 1.5 之前,2纠拔,3 步的順序是不能保證的秉剑,也就是可以是 1-2-3的順序執(zhí)行,也可能是 1-3-2 的執(zhí)行順序侦鹏。所以,但是如果是后面這種情況,在 A 線程已經(jīng)執(zhí)行完 1-3 步時匹耕,sInstance 已經(jīng)非空了荠雕,這時 B 線程執(zhí)行 getInstance 方法發(fā)現(xiàn)是非空的,直接拿走 sInstance 使用炸卑,就會導(dǎo)致出錯。

好的是嘱蛋,JVM 1.5 之后五续,可以通過 volatile 解決此問題。

雙重校驗(yàn)鎖模式資源利用率高凶伙,但是也同樣存在第一次加載反應(yīng)稍慢的問題它碎。

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

public class Singleton {
    private Singleton() {}
    public static Singleton getsInstance() {
        return Holder.sInstance;
    }

    private static class Holder {
        private static final Singleton sInstance = new Singleton();
    }
}

巧妙利用了虛擬機(jī)加載靜態(tài)內(nèi)部類的時機(jī)和方式显押,不僅確保了線程安全傻挂,也保證實(shí)例對象的唯一性。
sInstance 在第一次調(diào)用 getInstance 方法時才初始化蝉仇,所以也是延遲初始化殖蚕。

這種模式是最推薦的實(shí)現(xiàn)方式。

5. 枚舉模式

public enum SingletonEnum {
        SINGLETON;
        public void method() {
            ...
        }
    }

枚舉類是實(shí)現(xiàn)單例的最簡單的模式害驹。
枚舉和 Java 的普通類一樣蛤育,能有字段宛官、方法底洗,而且默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的咕娄,并且在任何情況下都是一個單例。

總結(jié)

單例模式是設(shè)計(jì)模式里面最基礎(chǔ)的也是使用較為頻繁的模式圣勒。



相關(guān)文章:
設(shè)計(jì)模式整理(1) 代理模式
設(shè)計(jì)模式整理(2) 單例模式
設(shè)計(jì)模式整理(3) Builder 模式
設(shè)計(jì)模式整理(4) 原型模式
設(shè)計(jì)模式整理(5) 工廠模式
設(shè)計(jì)模式整理(6) 策略模式
設(shè)計(jì)模式整理(7) 狀態(tài)模式
設(shè)計(jì)模式整理(8) 責(zé)任鏈模式
設(shè)計(jì)模式整理(9) 觀察者模式
設(shè)計(jì)模式整理(10) 適配器模式
設(shè)計(jì)模式整理(11) 裝飾模式
設(shè)計(jì)模式整理(12) 中介者模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末挚歧,一起剝皮案震驚了整個濱河市吁峻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌用含,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凡傅,死亡現(xiàn)場離奇詭異肠缔,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)槽华,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進(jìn)店門猫态,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人亲雪,你說我怎么就攤上這事∠罕辏” “怎么了灌砖?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蘸吓。 經(jīng)常有香客問我撩幽,道長,這世上最難降的妖魔是什么摸航? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任酱虎,我火速辦了婚禮擂涛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恢暖。我一直安慰自己狰右,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布嫁佳。 她就那樣靜靜地躺著,像睡著了一般盛垦。 火紅的嫁衣襯著肌膚如雪瓤漏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天蝶俱,我揣著相機(jī)與錄音娃惯,去河邊找鬼。 笑死趾浅,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的皿哨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼如输,長吁一口氣:“原來是場噩夢啊……” “哼央勒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起稳吮,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤灶似,失蹤者是張志新(化名)和其女友劉穎瑞你,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體春感,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡虏缸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了刀疙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谦秧。...
    茶點(diǎn)故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锥累,靈堂內(nèi)的尸體忽然破棺而出集歇,到底是詐尸還是另有隱情,我是刑警寧澤际歼,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布鹅心,位于F島的核電站纺荧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏输枯。R本人自食惡果不足惜占贫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一桃熄、第九天 我趴在偏房一處隱蔽的房頂上張望蜻拨。 院中可真熱鬧桩引,春花似錦坑匠、人聲如沸卧惜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闪朱。三九已至,卻和暖如春奋姿,著一層夾襖步出監(jiān)牢的瞬間称诗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工癣诱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狡刘,地道東北人。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像疾就,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子猬腰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評論 2 354

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