設(shè)計(jì)模式 之 Singleton(Java實(shí)現(xiàn))

目錄

引言

開發(fā)中什么設(shè)計(jì)模式最常用? Singleton, Factory, ...

這些常用模式中哪個(gè)最簡(jiǎn)單? Singleton, ...

恭喜你"答對(duì)"了! Singleton確實(shí)是一個(gè)比較"簡(jiǎn)單"的模式

But -- 肯定要有But的, 不然就沒必要有下文了

Singleton如此"簡(jiǎn)單"的模式很多人卻都會(huì)犯錯(cuò)!

教科書版本

private static Singleton instance = null;

private Singleton() {
}

public static Singleton getInstance() {
    if (instance == null) {             // step 1
        instance = new Singleton();     // step 2
    }
    return instance;
}

這個(gè)版本最簡(jiǎn)單, 但是問題也是最多的, 那么有哪些問題呢?

  • 線程1: Step 1 (Done) -> Step 2 (Doing)

于此同時(shí)

  • 線程2: Step 1 (Done, 因?yàn)閕nstance == null) -> Step 2

這樣就創(chuàng)建了多個(gè)實(shí)例

Synchronized Method版本

public static synchronized Singleton getInstance() {
    if (instance == null) {                 // step 1
        instance = new Singleton();         // step 2
    }
    return instance;
}

這個(gè)版本看起來似乎很安全, 但是

  • Synchronized Method同時(shí)只能被一個(gè)線程調(diào)用, 該線程調(diào)用結(jié)束后才能被其他的線程調(diào)用 => 多線程調(diào)用效率受到影響

Synchronized Block版本

public static Singleton getSingleton() {
    if (instance == null) {                 // step 1
        synchronized (Singleton.class) {    // step 2
            instance = new Singleton();     // step 3
        }
    }
    return instance;
}

上面的Synchronized Method方法Synchronized的范圍是整個(gè)方法, 而Synchronized Block方法將Synchronized的范圍縮小為Block

看起來算是個(gè)改進(jìn), 但是卻引入了問題

  • 線程1: Step 1 (Done) -> Step 2 (Done) -> Step 3 (Doing)

于此同時(shí)

  • 線程2: Step 1 (Done) -> Step 2 (Waiting)

請(qǐng)注意下面的情節(jié), 此時(shí)線程1的Step 3完成了, 即

  • 線程1: ... -> Step 3 (Done)

  • 線程1: ... -> Step 2 (Done, 此時(shí)結(jié)束waiting) -> Step 3

結(jié)果和教科書版本一樣, 又創(chuàng)建了多個(gè)實(shí)例

Double Checked Locking(雙重檢驗(yàn)鎖)版本

此版本是對(duì)上述Synchronized Block版本的改進(jìn), 即在Synchronized Block內(nèi)部又添加了instance == null的判斷

public static Singleton getSingleton() {
    if (instance == null) {                 // step 1
        synchronized (Singleton.class) {    // step 2
            if (instance == null) {         // step 3
                instance = new Singleton(); // step 4
            }
        }
    }
    return instance;
}

寫到這里的時(shí)候, 我已經(jīng)"厭倦了": 不就寫個(gè)單例么, 加這么多判斷, 同步, 保護(hù)難道還有問題不成?!

遺憾的是, 還真是有問題! 問題主要出在Step 4

因?yàn)閕nstance = new Singleton()并非是一個(gè)原子操作

它由以下三個(gè)步驟

  • temp = allocate() => 分配內(nèi)存

  • constructor(temp) => 構(gòu)造對(duì)象

  • instance = temp => 賦值操作

但JVM存在指令重排序(Re-Order)優(yōu)化, 導(dǎo)致以上步驟2(構(gòu)造對(duì)象)和步驟3(賦值操作)的順序并不是固定的!

如果步驟3(賦值操作)先于步驟2(構(gòu)造對(duì)象), 那么有可能發(fā)生的問題是

  • 線程1: Step 1 (Done) -> Step 2 (Done) -> Step 3 (Done) -> Step 4 (分配內(nèi)存Done, 賦值操作Done, 構(gòu)造對(duì)象Doing)

于此同時(shí)

  • 線程2: Step 1 (return, 因?yàn)榇藭r(shí)instance != null)

但是線程2得到的instance是還沒有完全構(gòu)造的對(duì)象, 后果可想而知

Double Checked Locking(雙重檢驗(yàn)鎖)+volatile版本

此版本是對(duì)上述Double Checked Locking Pattern版本的改進(jìn), 即在instance成員前加上volatile修飾符, 以禁止JVM指令重排序(Re-Order)優(yōu)化

完整的代碼是這樣的

private volatile static Singleton instance = null;

private Singleton() {
}

public static Singleton getSingleton() {
    if (instance == null) {                 // step 1
        synchronized (Singleton.class) {    // step 2
            if (instance == null) {         // step 3
                instance = new Singleton(); // step 4
            }
        }
    }
    return instance;
}

好吧, 饒了一大圈之后, 總算又搞定了一個(gè)和Synchronized Method版本一樣可靠的版本

但是, 可靠不代表效率高, 而且為了創(chuàng)建一個(gè)單例, 寫上面一大坨代碼

又是synchronized, 又是雙重判斷, 最后連volatile都搬出來了, 你喜歡么?

關(guān)于使用volatile修飾符效率的討論和優(yōu)化, 詳細(xì)可以參考Java 單例真的寫對(duì)了么?

Static Factory版本

上述所有版本要么是有缺陷, 要么是效率低, 但是他們都有個(gè)共同的特點(diǎn): Lazy Loaded(懶加載)

如果不考慮Lazy Loaded帶來的這些微小的內(nèi)存消耗和優(yōu)化的話, 下面的版本是我最喜歡的

private static final Singleton instance = new Singleton();

private Singleton() {
}

public static Singleton getInstance() {
    return instance;
}

現(xiàn)在知道我為什么最喜歡了吧, 因?yàn)樗娴暮芎?jiǎn)單!

這里的instance成員聲明成static final, 這意味著

在該類被加載至內(nèi)存時(shí)就創(chuàng)建了實(shí)例, 該過程自然是Thread Safe(線程安全)的

Enum版本

這個(gè)版本很"高端", 不過缺點(diǎn)也很明顯: 太"高端", 以至于之前我完全沒有接觸和使用過, 不過為了文章的完整性, 還是在此簡(jiǎn)單討論下吧

public enum Singleton{
    INSTANCE;
}

什么? 這就完了? 果然太"高端"! 這么神奇, 原理是怎樣呢? 黑魔法就是

默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的

我們可以通過Singleton來訪問實(shí)例, 使用也是如此簡(jiǎn)單

附錄

更多文章, 請(qǐng)支持我的個(gè)人博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市褐奥,隨后出現(xiàn)的幾起案子签舞,更是在濱河造成了極大的恐慌成翩,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異烦绳,居然都是意外死亡澄港,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門誉简,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碉就,“玉大人,你說我怎么就攤上這事闷串∥驮浚” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵烹吵,是天一觀的道長(zhǎng)碉熄。 經(jīng)常有香客問我,道長(zhǎng)肋拔,這世上最難降的妖魔是什么锈津? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮凉蜂,結(jié)果婚禮上琼梆,老公的妹妹穿的比我還像新娘。我一直安慰自己窿吩,他們只是感情好茎杂,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著纫雁,像睡著了一般煌往。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上轧邪,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天刽脖,我揣著相機(jī)與錄音羞海,去河邊找鬼。 笑死曾棕,一個(gè)胖子當(dāng)著我的面吹牛扣猫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翘地,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼申尤,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了衙耕?” 一聲冷哼從身側(cè)響起昧穿,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎橙喘,沒想到半個(gè)月后时鸵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡厅瞎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年饰潜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片和簸。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡彭雾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锁保,到底是詐尸還是另有隱情薯酝,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布爽柒,位于F島的核電站吴菠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏浩村。R本人自食惡果不足惜做葵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望穴亏。 院中可真熱鬧蜂挪,春花似錦、人聲如沸嗓化。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刺覆。三九已至严肪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驳糯。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工篇梭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酝枢。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓恬偷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親帘睦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子袍患,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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