17.8.25從偶像的偶像轉(zhuǎn)發(fā)JAVA并發(fā)單例模式設(shè)計(jì)的步驟

轉(zhuǎn)自:http://bbs.csdn.net/topics/391840031苹享,作者:tuoni123

在GoF的23種設(shè)計(jì)模式中双絮,單例模式是比較簡(jiǎn)單的一種。然而,有時(shí)候越是簡(jiǎn)單的東西越容易出現(xiàn)問題囤攀。下面就單例設(shè)計(jì)模式詳細(xì)的探討一下软免。

所謂單例模式,簡(jiǎn)單來說焚挠,就是在整個(gè)應(yīng)用中保證只有一個(gè)類的實(shí)例存在膏萧。就像是Java?Web中的application,也就是提供了一個(gè)全局變量蝌衔,用處相當(dāng)廣泛榛泛,比如保存全局?jǐn)?shù)據(jù),實(shí)現(xiàn)全局性的操作等噩斟。

1.?最簡(jiǎn)單的實(shí)現(xiàn)

首先挟鸠,能夠想到的最簡(jiǎn)單的實(shí)現(xiàn)是,把類的構(gòu)造函數(shù)寫成private的亩冬,從而保證別的類不能實(shí)例化此類艘希,然后在類中提供一個(gè)靜態(tài)的實(shí)例并能夠返回給使用者。這樣硅急,使用者就可以通過這個(gè)引用使用到這個(gè)類的實(shí)例了覆享。

public?class?SingletonClass?{

private?static?final?SingletonClass?instance?=?new?SingletonClass();

public?static?SingletonClass?getInstance()?{

return?instance;

}

private?SingletonClass()?{

}

}

如上例,外部使用者如果需要使用SingletonClass的實(shí)例营袜,只能通過getInstance()方法撒顿,并且它的構(gòu)造方法是private的,這樣就保證了只能有一個(gè)對(duì)象存在荚板。

2.?性能優(yōu)化——lazy?loaded

上面的代碼雖然簡(jiǎn)單凤壁,但是有一個(gè)問題——無論這個(gè)類是否被使用,都會(huì)創(chuàng)建一個(gè)instance對(duì)象跪另。如果這個(gè)創(chuàng)建過程很耗時(shí)拧抖,比如需要連接10000次數(shù)據(jù)庫(夸張了…:-)),并且這個(gè)類還并不一定會(huì)被使用免绿,那么這個(gè)創(chuàng)建過程就是無用的唧席。怎么辦呢?

為了解決這個(gè)問題嘲驾,我們想到了新的解決方案:

public?class?SingletonClass?{

private?static?SingletonClass?instance?=?null;

public?static?SingletonClass?getInstance()?{

if(instance?==?null)?{

instance?=?new?SingletonClass();

}

return?instance;

}

private?SingletonClass()?{

}

}

代碼的變化有兩處——首先淌哟,把instance初始化為null,直到第一次使用的時(shí)候通過判斷是否為null來創(chuàng)建對(duì)象辽故。因?yàn)閯?chuàng)建過程不在聲明處徒仓,所以那個(gè)final的修飾必須去掉。

我們來想象一下這個(gè)過程誊垢。要使用SingletonClass掉弛,調(diào)用getInstance()方法喻杈。第一次的時(shí)候發(fā)現(xiàn)instance是null,然后就新建一個(gè)對(duì)象狰晚,返回出去筒饰;第二次再使用的時(shí)候,因?yàn)檫@個(gè)instance是static的壁晒,所以已經(jīng)不是null了瓷们,因此不會(huì)再創(chuàng)建對(duì)象,直接將其返回秒咐。

這個(gè)過程就成為lazy?loaded谬晕,也就是遲加載——直到使用的時(shí)候才進(jìn)行加載。

3.?同步

上面的代碼很清楚携取,也很簡(jiǎn)單攒钳。然而就像那句名言:“80%的錯(cuò)誤都是由20%代碼優(yōu)化引起的”。單線程下雷滋,這段代碼沒有什么問題不撑,可是如果是多線程,麻煩就來了晤斩。我們來分析一下:

線程A希望使用SingletonClass焕檬,調(diào)用getInstance()方法。因?yàn)槭堑谝淮握{(diào)用澳泵,A就發(fā)現(xiàn)instance是null的实愚,于是它開始創(chuàng)建實(shí)例,就在這個(gè)時(shí)候兔辅,CPU發(fā)生時(shí)間片切換腊敲,線程B開始執(zhí)行,它要使用SingletonClass维苔,調(diào)用getInstance()方法碰辅,同樣檢測(cè)到instance是null——注意,這是在A檢測(cè)完之后切換的蕉鸳,也就是說A并沒有來得及創(chuàng)建對(duì)象——因此B開始創(chuàng)建乎赴。B創(chuàng)建完成后,切換到A繼續(xù)執(zhí)行潮尝,因?yàn)樗呀?jīng)檢測(cè)完了,所以A不會(huì)再檢測(cè)一遍饿序,它會(huì)直接創(chuàng)建對(duì)象勉失。這樣,線程A和B各自擁有一個(gè)SingletonClass的對(duì)象——單例失斣健乱凿!

解決的方法也很簡(jiǎn)單顽素,那就是加鎖:

public?class?SingletonClass?{

private?static?SingletonClass?instance?=?null;

public?synchronized?static?SingletonClass?getInstance()?{

if(instance?==?null)?{

instance?=?new?SingletonClass();

}

return?instance;

}

private?SingletonClass()?{

}

}

是要getInstance()加上同步鎖,一個(gè)線程必須等待另外一個(gè)線程創(chuàng)建完成后才能使用這個(gè)方法徒蟆,這就保證了單例的唯一性胁出。

4.?又是性能

上面的代碼又是很清楚很簡(jiǎn)單的,然而段审,簡(jiǎn)單的東西往往不夠理想全蝶。這段代碼毫無疑問存在性能的問題——synchronized修飾的同步塊可是要比一般的代碼段慢上幾倍的!如果存在很多次getInstance()的調(diào)用寺枉,那性能問題就不得不考慮了抑淫!

讓我們來分析一下,究竟是整個(gè)方法都必須加鎖姥闪,還是僅僅其中某一句加鎖就足夠了始苇?我們?yōu)槭裁匆渔i呢?分析一下出現(xiàn)lazy?loaded的那種情形的原因筐喳。原因就是檢測(cè)null的操作和創(chuàng)建對(duì)象的操作分離了催式。如果這兩個(gè)操作能夠原子地進(jìn)行,那么單例就已經(jīng)保證了避归。于是蓄氧,我們開始修改代碼:

public?class?SingletonClass?{

private?static?SingletonClass?instance?=?null;

public?static?SingletonClass?getInstance()?{

synchronized?(SingletonClass.class)?{

if(instance?==?null)?{

instance?=?new?SingletonClass();

}

}

return?instance;

}

private?SingletonClass()?{

}

}

首先去掉getInstance()的同步操作宪塔,然后把同步鎖加載if語句上震蒋。但是這樣的修改起不到任何作用:因?yàn)槊看握{(diào)用getInstance()的時(shí)候必然要同步骇吭,性能問題還是存在课锌。如果……如果我們事先判斷一下是不是為null再去同步呢浩村?

public?class?SingletonClass?{

private?static?SingletonClass?instance?=?null;

public?static?SingletonClass?getInstance()?{

if?(instance?==?null)?{

synchronized?(SingletonClass.class)?{

if?(instance?==?null)?{

instance?=?new?SingletonClass();

}

}

}

return?instance;

}

private?SingletonClass()?{

}

}

還有問題嗎犀忱?首先判斷instance是不是為null得院,如果為null左电,加鎖初始化牌废;如果不為null咽白,直接返回instance。

這就是double-checked?locking設(shè)計(jì)實(shí)現(xiàn)單例模式鸟缕。到此為止晶框,一切都很完美。我們用一種很聰明的方式實(shí)現(xiàn)了單例模式懂从。

5.?從源頭檢查

下面我們開始說編譯原理授段。所謂編譯,就是把源代碼“翻譯”成目標(biāo)代碼——大多數(shù)是指機(jī)器代碼——的過程番甩。針對(duì)Java侵贵,它的目標(biāo)代碼不是本地機(jī)器代碼,而是虛擬機(jī)代碼缘薛。編譯原理里面有一個(gè)很重要的內(nèi)容是編譯器優(yōu)化窍育。所謂編譯器優(yōu)化是指卡睦,在不改變?cè)瓉碚Z義的情況下,通過調(diào)整語句順序漱抓,來讓程序運(yùn)行的更快表锻。這個(gè)過程成為reorder。

要知道乞娄,JVM只是一個(gè)標(biāo)準(zhǔn)瞬逊,并不是實(shí)現(xiàn)。JVM中并沒有規(guī)定有關(guān)編譯器優(yōu)化的內(nèi)容补胚,也就是說码耐,JVM實(shí)現(xiàn)可以自由的進(jìn)行編譯器優(yōu)化。

下面來想一下溶其,創(chuàng)建一個(gè)變量需要哪些步驟呢骚腥?一個(gè)是申請(qǐng)一塊內(nèi)存,調(diào)用構(gòu)造方法進(jìn)行初始化操作瓶逃,另一個(gè)是分配一個(gè)指針指向這塊內(nèi)存束铭。這兩個(gè)操作誰在前誰在后呢?JVM規(guī)范并沒有規(guī)定厢绝。那么就存在這么一種情況契沫,JVM是先開辟出一塊內(nèi)存,然后把指針指向這塊內(nèi)存昔汉,最后調(diào)用構(gòu)造方法進(jìn)行初始化懈万。

下面我們來考慮這么一種情況:線程A開始創(chuàng)建SingletonClass的實(shí)例,此時(shí)線程B調(diào)用了getInstance()方法靶病,首先判斷instance是否為null会通。按照我們上面所說的內(nèi)存模型,A已經(jīng)把instance指向了那塊內(nèi)存娄周,只是還沒有調(diào)用構(gòu)造方法涕侈,因此B檢測(cè)到instance不為null,于是直接把instance返回了——問題出現(xiàn)了煤辨,盡管instance不為null裳涛,但它并沒有構(gòu)造完成,就像一套房子已經(jīng)給了你鑰匙众辨,但你并不能住進(jìn)去端三,因?yàn)槔锩孢€沒有收拾。此時(shí)泻轰,如果B在A將instance構(gòu)造完成之前就是用了這個(gè)實(shí)例技肩,程序就會(huì)出現(xiàn)錯(cuò)誤了!

于是浮声,我們想到了下面的代碼:

public?class?SingletonClass?{

private?static?SingletonClass?instance?=?null;

public?static?SingletonClass?getInstance()?{

if?(instance?==?null)?{

SingletonClass?sc;

synchronized?(SingletonClass.class)?{

sc?=?instance;

if?(sc?==?null)?{

synchronized?(SingletonClass.class)?{

if(sc?==?null)?{

sc?=?new?SingletonClass();

}

}

instance?=?sc;

}

}

}

return?instance;

}

private?SingletonClass()?{

}

}

我們?cè)诘谝粋€(gè)同步塊里面創(chuàng)建一個(gè)臨時(shí)變量虚婿,然后使用這個(gè)臨時(shí)變量進(jìn)行對(duì)象的創(chuàng)建,并且在最后把instance指針臨時(shí)變量的內(nèi)存空間泳挥。寫出這種代碼基于以下思想然痊,即synchronized會(huì)起到一個(gè)代碼屏蔽的作用,同步塊里面的代碼和外部的代碼沒有聯(lián)系屉符。因此剧浸,在外部的同步塊里面對(duì)臨時(shí)變量sc進(jìn)行操作并不影響instance,所以外部類在instance=sc;之前檢測(cè)instance的時(shí)候矗钟,結(jié)果instance依然是null唆香。

不過,這種想法完全是錯(cuò)誤的吨艇!同步塊的釋放保證在此之前——也就是同步塊里面——的操作必須完成躬它,但是并不保證同步塊之后的操作不能因編譯器優(yōu)化而調(diào)換到同步塊結(jié)束之前進(jìn)行。因此东涡,編譯器完全可以把instance=sc;這句移到內(nèi)部同步塊里面執(zhí)行冯吓。這樣,程序又是錯(cuò)誤的了疮跑!

6.?解決方案

說了這么多组贺,難道單例沒有辦法在Java中實(shí)現(xiàn)嗎?其實(shí)不然祖娘!

在JDK?5之后失尖,Java使用了新的內(nèi)存模型。volatile關(guān)鍵字有了明確的語義——在JDK1.5之前渐苏,volatile是個(gè)關(guān)鍵字掀潮,但是并沒有明確的規(guī)定其用途——被volatile修飾的寫變量不能和之前的讀寫代碼調(diào)整,讀變量不能和之后的讀寫代碼調(diào)整整以!因此胧辽,只要我們簡(jiǎn)單的把instance加上volatile關(guān)鍵字就可以了。

public?class?SingletonClass?{

private?volatile?static?SingletonClass?instance?=?null;

public?static?SingletonClass?getInstance()?{

if?(instance?==?null)?{

synchronized?(SingletonClass.class)?{

if(instance?==?null)?{

instance?=?new?SingletonClass();

}

}

}

return?instance;

}

private?SingletonClass()?{

}

}

然而公黑,這只是JDK1.5之后的Java的解決方案邑商,那之前版本呢?其實(shí)凡蚜,還有另外的一種解決方案人断,并不會(huì)受到Java版本的影響:

public?class?SingletonClass?{

private?static?class?SingletonClassInstance?{

private?static?final?SingletonClass?instance?=?new?SingletonClass();

}

public?static?SingletonClass?getInstance()?{

return?SingletonClassInstance.instance;

}

private?SingletonClass()?{

}

}

在這一版本的單例模式實(shí)現(xiàn)代碼中,我們使用了Java的靜態(tài)內(nèi)部類朝蜘。這一技術(shù)是被JVM明確說明了的恶迈,因此不存在任何二義性。在這段代碼中,因?yàn)镾ingletonClass沒有static的屬性暇仲,因此并不會(huì)被初始化步做。直到調(diào)用getInstance()的時(shí)候,會(huì)首先加載SingletonClassInstance類奈附,這個(gè)類有一個(gè)static的SingletonClass實(shí)例全度,因此需要調(diào)用SingletonClass的構(gòu)造方法,然后getInstance()將把這個(gè)內(nèi)部類的instance返回給使用者斥滤。由于這個(gè)instance是static的将鸵,因此并不會(huì)構(gòu)造多次。

由于SingletonClassInstance是私有靜態(tài)內(nèi)部類佑颇,所以不會(huì)被其他類知道顶掉,同樣,static語義也要求不會(huì)有多個(gè)實(shí)例存在挑胸。并且痒筒,JSL規(guī)范定義,類的構(gòu)造必須是原子性的嗜暴,非并發(fā)的凸克,因此不需要加同步塊。同樣闷沥,由于這個(gè)構(gòu)造是并發(fā)的萎战,所以getInstance()也并不需要加同步。

至此舆逃,我們完整的了解了單例模式在Java語言中的時(shí)候蚂维,提出了兩種解決方案。個(gè)人偏向于第二種路狮,并且Effiective?Java也推薦的這種方式虫啥。

作者:coding_lcc

鏈接:http://www.reibang.com/p/12c4fd1d7000

來源:簡(jiǎn)書

著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)奄妨,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處涂籽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市砸抛,隨后出現(xiàn)的幾起案子评雌,更是在濱河造成了極大的恐慌,老刑警劉巖直焙,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件景东,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡奔誓,警方通過查閱死者的電腦和手機(jī)斤吐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人和措,你說我怎么就攤上這事庄呈。” “怎么了臼婆?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵抒痒,是天一觀的道長(zhǎng)幌绍。 經(jīng)常有香客問我颁褂,道長(zhǎng),這世上最難降的妖魔是什么傀广? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任颁独,我火速辦了婚禮,結(jié)果婚禮上伪冰,老公的妹妹穿的比我還像新娘誓酒。我一直安慰自己,他們只是感情好贮聂,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布靠柑。 她就那樣靜靜地躺著,像睡著了一般吓懈。 火紅的嫁衣襯著肌膚如雪歼冰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天耻警,我揣著相機(jī)與錄音隔嫡,去河邊找鬼。 笑死甘穿,一個(gè)胖子當(dāng)著我的面吹牛腮恩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播温兼,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼秸滴,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了募判?” 一聲冷哼從身側(cè)響起荡含,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎兰伤,沒想到半個(gè)月后内颗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡敦腔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年均澳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡找前,死狀恐怖糟袁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情躺盛,我是刑警寧澤项戴,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站槽惫,受9級(jí)特大地震影響周叮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜界斜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一仿耽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧各薇,春花似錦项贺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至林螃,卻和暖如春奕删,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背治宣。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國打工急侥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侮邀。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓坏怪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親绊茧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子铝宵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

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