Java單例模式——并非看起來(lái)那么簡(jiǎn)單

原文出處:?http://blog.csdn.net/goodlixueyong/article/details/51935526

Java中單例(Singleton)模式是一種廣泛使用的設(shè)計(jì)模式而克。單例模式的主要作用是保證在Java程序中樱哼,某個(gè)類只有一個(gè)實(shí)例存在第岖。一些管理器和控制器常被設(shè)計(jì)成單例模式。

? ? ? ?單例模式有很多好處入桂,它能夠避免實(shí)例對(duì)象的重復(fù)創(chuàng)建蝶防,不僅可以減少每次創(chuàng)建對(duì)象的時(shí)間開(kāi)銷,還可以節(jié)約內(nèi)存空間跨跨;能夠避免由于操作多個(gè)實(shí)例導(dǎo)致的邏輯錯(cuò)誤悼做。如果一個(gè)對(duì)象有可能貫穿整個(gè)應(yīng)用程序疯特,而且起到了全局統(tǒng)一管理控制的作用,那么單例模式也許是一個(gè)值得考慮的選擇肛走。

? ? ? ?單例模式有很多種寫(xiě)法漓雅,大部分寫(xiě)法都或多或少有一些不足。下面將分別對(duì)這幾種寫(xiě)法進(jìn)行介紹朽色。

1邻吞、餓漢模式

[java]?view plain?copy

public?class?Singleton{??

private?static?Singleton?instance?=?new?Singleton();??

private?Singleton(){}??

public?static?Singleton?newInstance(){??

return?instance;??

????}??

}??

? ? ? ?從代碼中我們看到,類的構(gòu)造函數(shù)定義為private的葫男,保證其他類不能實(shí)例化此類抱冷,然后提供了一個(gè)靜態(tài)實(shí)例并返回給調(diào)用者。餓漢模式是最簡(jiǎn)單的一種實(shí)現(xiàn)方式腾誉,餓漢模式在類加載的時(shí)候就對(duì)實(shí)例進(jìn)行創(chuàng)建徘层,實(shí)例在整個(gè)程序周期都存在。它的好處是只在類加載的時(shí)候創(chuàng)建一次實(shí)例利职,不會(huì)存在多個(gè)線程創(chuàng)建多個(gè)實(shí)例的情況趣效,避免了多線程同步的問(wèn)題。它的缺點(diǎn)也很明顯猪贪,即使這個(gè)單例沒(méi)有用到也會(huì)被創(chuàng)建跷敬,而且在類加載之后就被創(chuàng)建,內(nèi)存就被浪費(fèi)了热押。

? ? ? ?這種實(shí)現(xiàn)方式適合單例占用內(nèi)存比較小西傀,在初始化時(shí)就會(huì)被用到的情況。但是桶癣,如果單例占用的內(nèi)存比較大拥褂,或單例只是在某個(gè)特定場(chǎng)景下才會(huì)用到,使用餓漢模式就不合適了牙寞,這時(shí)候就需要用到懶漢模式進(jìn)行延遲加載饺鹃。

2、懶漢模式

[java]?view plain?copy

public?class?Singleton{??

private?static?Singleton?instance?=?null;??

private?Singleton(){}??

public?static?Singleton?newInstance(){??

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

instance?=new?Singleton();??

????????}??

return?instance;??

????}??

}??

? ? ? ?懶漢模式中單例是在需要的時(shí)候才去創(chuàng)建的间雀,如果單例已經(jīng)創(chuàng)建悔详,再次調(diào)用獲取接口將不會(huì)重新創(chuàng)建新的對(duì)象,而是直接返回之前創(chuàng)建的對(duì)象惹挟。如果某個(gè)單例使用的次數(shù)少茄螃,并且創(chuàng)建單例消耗的資源較多,那么就需要實(shí)現(xiàn)單例的按需創(chuàng)建连锯,這個(gè)時(shí)候使用懶漢模式就是一個(gè)不錯(cuò)的選擇归苍。但是這里的懶漢模式并沒(méi)有考慮線程安全問(wèn)題,在多個(gè)線程可能會(huì)并發(fā)調(diào)用它的getInstance()方法运怖,導(dǎo)致創(chuàng)建多個(gè)實(shí)例霜医,因此需要加鎖解決線程同步問(wèn)題,實(shí)現(xiàn)如下驳规。

[java]?view plain?copy

public?class?Singleton{??

private?static?Singleton?instance?=?null;??

private?Singleton(){}??

public?static?synchronized?Singleton?newInstance(){??

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

instance?=new?Singleton();??

????????}??

return?instance;??

????}??

}??

3肴敛、雙重校驗(yàn)鎖

? ? ? ?加鎖的懶漢模式看起來(lái)即解決了線程并發(fā)問(wèn)題,又實(shí)現(xiàn)了延遲加載吗购,然而它存在著性能問(wèn)題医男,依然不夠完美。synchronized修飾的同步方法比一般方法要慢很多捻勉,如果多次調(diào)用getInstance()镀梭,累積的性能損耗就比較大了。因此就有了雙重校驗(yàn)鎖踱启,先看下它的實(shí)現(xiàn)代碼报账。

[java]?view plain?copy

public?class?Singleton?{??

private?static?Singleton?instance?=?null;??

private?Singleton(){}??

public?static?Singleton?getInstance()?{??

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

synchronized?(Singleton.class)?{??

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

instance?=new?Singleton();??

????????????????}??

????????????}??

????????}??

return?instance;??

????}??

}??

? ? ? ?可以看到上面在同步代碼塊外多了一層instance為空的判斷研底。由于單例對(duì)象只需要?jiǎng)?chuàng)建一次,如果后面再次調(diào)用getInstance()只需要直接返回單例對(duì)象透罢。因此榜晦,大部分情況下,調(diào)用getInstance()都不會(huì)執(zhí)行到同步代碼塊羽圃,從而提高了程序性能乾胶。不過(guò)還需要考慮一種情況,假如兩個(gè)線程A朽寞、B识窿,A執(zhí)行了if (instance == null)語(yǔ)句,它會(huì)認(rèn)為單例對(duì)象沒(méi)有創(chuàng)建脑融,此時(shí)線程切到B也執(zhí)行了同樣的語(yǔ)句喻频,B也認(rèn)為單例對(duì)象沒(méi)有創(chuàng)建,然后兩個(gè)線程依次執(zhí)行同步代碼塊肘迎,并分別創(chuàng)建了一個(gè)單例對(duì)象半抱。為了解決這個(gè)問(wèn)題,還需要在同步代碼塊中增加if (instance == null)語(yǔ)句膜宋,也就是上面看到的代碼2窿侈。

我們看到雙重校驗(yàn)鎖即實(shí)現(xiàn)了延遲加載,又解決了線程并發(fā)問(wèn)題秋茫,同時(shí)還解決了執(zhí)行效率問(wèn)題史简,是否真的就萬(wàn)無(wú)一失了呢?

? ? ? ?這里要提到Java中的指令重排優(yōu)化肛著。所謂指令重排優(yōu)化是指在不改變?cè)Z(yǔ)義的情況下圆兵,通過(guò)調(diào)整指令的執(zhí)行順序讓程序運(yùn)行的更快。JVM中并沒(méi)有規(guī)定編譯器優(yōu)化相關(guān)的內(nèi)容枢贿,也就是說(shuō)JVM可以自由的進(jìn)行指令重排序的優(yōu)化殉农。

? ? ? ?這個(gè)問(wèn)題的關(guān)鍵就在于由于指令重排優(yōu)化的存在,導(dǎo)致初始化Singleton和將對(duì)象地址賦給instance字段的順序是不確定的局荚。在某個(gè)線程創(chuàng)建單例對(duì)象時(shí)超凳,在構(gòu)造方法被調(diào)用之前,就為該對(duì)象分配了內(nèi)存空間并將對(duì)象的字段設(shè)置為默認(rèn)值耀态。此時(shí)就可以將分配的內(nèi)存地址賦值給instance字段了轮傍,然而該對(duì)象可能還沒(méi)有初始化。若緊接著另外一個(gè)線程來(lái)調(diào)用getInstance首装,取到的就是狀態(tài)不正確的對(duì)象创夜,程序就會(huì)出錯(cuò)。

? ? ? ?以上就是雙重校驗(yàn)鎖會(huì)失效的原因仙逻,不過(guò)還好在JDK1.5及之后版本增加了volatile關(guān)鍵字驰吓。volatile的一個(gè)語(yǔ)義是禁止指令重排序優(yōu)化涧尿,也就保證了instance變量被賦值的時(shí)候?qū)ο笠呀?jīng)是初始化過(guò)的,從而避免了上面說(shuō)到的問(wèn)題檬贰。代碼如下:

[java]?view plain?copy

public?class?Singleton?{??

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

private?Singleton(){}??

public?static?Singleton?getInstance()?{??

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

synchronized?(Singleton.class)?{??

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

instance?=new?Singleton();??

????????????????}??

????????????}??

????????}??

return?instance;??

????}??

}??

4姑廉、靜態(tài)內(nèi)部類

? ? ? ?除了上面的三種方式,還有另外一種實(shí)現(xiàn)單例的方式偎蘸,通過(guò)靜態(tài)內(nèi)部類來(lái)實(shí)現(xiàn)庄蹋。首先看一下它的實(shí)現(xiàn)代碼:

[java]?view plain?copy

public?class?Singleton{??

private?static?class?SingletonHolder{??

public?static?Singleton?instance?=?new?Singleton();??

????}??

private?Singleton(){}??

public?static?Singleton?newInstance(){??

return?SingletonHolder.instance;??

????}??

}??

? ? ? ?這種方式同樣利用了類加載機(jī)制來(lái)保證只創(chuàng)建一個(gè)instance實(shí)例瞬内。它與餓漢模式一樣迷雪,也是利用了類加載機(jī)制,因此不存在多線程并發(fā)的問(wèn)題虫蝶。不一樣的是章咧,它是在內(nèi)部類里面去創(chuàng)建對(duì)象實(shí)例。這樣的話能真,只要應(yīng)用中不使用內(nèi)部類赁严,JVM就不會(huì)去加載這個(gè)單例類,也就不會(huì)創(chuàng)建單例對(duì)象粉铐,從而實(shí)現(xiàn)懶漢式的延遲加載疼约。也就是說(shuō)這種方式可以同時(shí)保證延遲加載和線程安全。

5蝙泼、枚舉

? ? ? ?再來(lái)看本文要介紹的最后一種實(shí)現(xiàn)方式:枚舉程剥。

[java]?view plain?copy

public?enum?Singleton{??

????instance;??

public?void?whateverMethod(){}??????

}??

? ? ? ?上面提到的四種實(shí)現(xiàn)單例的方式都有共同的缺點(diǎn):

1)需要額外的工作來(lái)實(shí)現(xiàn)序列化,否則每次反序列化一個(gè)序列化的對(duì)象時(shí)都會(huì)創(chuàng)建一個(gè)新的實(shí)例汤踏。

2)可以使用反射強(qiáng)行調(diào)用私有構(gòu)造器(如果要避免這種情況织鲸,可以修改構(gòu)造器,讓它在創(chuàng)建第二個(gè)實(shí)例的時(shí)候拋異常)溪胶。

? ? ? ?而枚舉類很好的解決了這兩個(gè)問(wèn)題搂擦,使用枚舉除了線程安全和防止反射調(diào)用構(gòu)造器之外,還提供了自動(dòng)序列化機(jī)制哗脖,防止反序列化的時(shí)候創(chuàng)建新的對(duì)象瀑踢。因此,《Effective Java》作者推薦使用的方法才避。不過(guò)丘损,在實(shí)際工作中,很少看見(jiàn)有人這么寫(xiě)工扎。

總結(jié)

? ? ? ?本文總結(jié)了五種Java中實(shí)現(xiàn)單例的方法徘钥,其中前兩種都不夠完美,雙重校驗(yàn)鎖和靜態(tài)內(nèi)部類的方式可以解決大部分問(wèn)題肢娘,平時(shí)工作中使用的最多的也是這兩種方式呈础。枚舉方式雖然很完美的解決了各種問(wèn)題舆驶,但是這種寫(xiě)法多少讓人感覺(jué)有些生疏。個(gè)人的建議是而钞,在沒(méi)有特殊需求的情況下沙廉,使用第三種和第四種方式實(shí)現(xiàn)單例模式。

參考文章:http://www.jfox.info/java-dan-li-mo-shi-de-ji-zhong-xie-fa

http://devbean.blog.51cto.com/448512/203501/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末臼节,一起剝皮案震驚了整個(gè)濱河市撬陵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌网缝,老刑警劉巖巨税,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異粉臊,居然都是意外死亡草添,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)扼仲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)远寸,“玉大人,你說(shuō)我怎么就攤上這事屠凶〕酆螅” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵矗愧,是天一觀的道長(zhǎng)灶芝。 經(jīng)常有香客問(wèn)我,道長(zhǎng)贱枣,這世上最難降的妖魔是什么监署? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮纽哥,結(jié)果婚禮上钠乏,老公的妹妹穿的比我還像新娘。我一直安慰自己春塌,他們只是感情好晓避,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著只壳,像睡著了一般俏拱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吼句,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天锅必,我揣著相機(jī)與錄音,去河邊找鬼。 笑死搞隐,一個(gè)胖子當(dāng)著我的面吹牛驹愚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播劣纲,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼逢捺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了癞季?” 一聲冷哼從身側(cè)響起劫瞳,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绷柒,沒(méi)想到半個(gè)月后志于,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辉巡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年恨憎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蕊退。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郊楣。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瓤荔,靈堂內(nèi)的尸體忽然破棺而出净蚤,到底是詐尸還是另有隱情,我是刑警寧澤输硝,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布今瀑,位于F島的核電站,受9級(jí)特大地震影響点把,放射性物質(zhì)發(fā)生泄漏橘荠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一郎逃、第九天 我趴在偏房一處隱蔽的房頂上張望哥童。 院中可真熱鬧,春花似錦褒翰、人聲如沸贮懈。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)朵你。三九已至,卻和暖如春揣非,著一層夾襖步出監(jiān)牢的瞬間抡医,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工早敬, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留忌傻,地道東北人毛仪。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像芯勘,于是被迫代替她去往敵國(guó)和親箱靴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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

  • 文章部分內(nèi)容轉(zhuǎn)載自:http://blog.csdn.net/zhangerqing 一荷愕、設(shè)計(jì)模式的分類 總體來(lái)說(shuō)...
    j_cong閱讀 2,065評(píng)論 0 20
  • 原文鏈接:http://blog.csdn.net/zhangerqing http://www.cnblogs....
    孤獨(dú)雜貨鋪閱讀 1,516評(píng)論 0 3
  • 一衡怀、設(shè)計(jì)模式的分類 總體來(lái)說(shuō)設(shè)計(jì)模式分為三大類: 創(chuàng)建型模式,共五種:工廠方法模式安疗、抽象工廠模式抛杨、單例模式、建造者...
    lichengjin閱讀 894評(píng)論 0 8
  • 一荐类、設(shè)計(jì)模式的分類 總體來(lái)說(shuō)設(shè)計(jì)模式分為三大類: 創(chuàng)建型模式怖现,共五種:工廠方法模式、抽象工廠模式玉罐、單例模式、建造者...
    RamboLI閱讀 749評(píng)論 0 1
  • 2017.02 19 星期一 晴 今天就要回漳州了季蚂,我又傷心又開(kāi)心,反正媽媽是很開(kāi)心扭屁。我和爸爸都想再多呆一天算谈,可媽...
    王紫楊閱讀 153評(píng)論 2 7