深入理解單例模式:靜態(tài)內(nèi)部類(lèi)單例原理

轉(zhuǎn)載:https://blog.csdn.net/mnb65482/article/details/80458571

本文主要介紹java的單例模式我磁,以及詳細(xì)剖析靜態(tài)內(nèi)部類(lèi)之所以能夠?qū)崿F(xiàn)單例的原理。OK,廢話不多說(shuō)汽纠,進(jìn)入正文媳危。

首先我們要先了解下單例的四大原則:

1.構(gòu)造私有歪玲。
2.以靜態(tài)方法或者枚舉返回實(shí)例呐舔。

3.確保實(shí)例只有一個(gè)淑趾,尤其是多線程環(huán)境烹棉。

4.確保反序列換時(shí)不會(huì)重新構(gòu)建對(duì)象攒霹。

我們常用的單例模式有:

餓漢模式、懶漢模式浆洗、雙重鎖懶漢模式催束、靜態(tài)內(nèi)部類(lèi)模式、枚舉模式伏社,我們來(lái)逐一分析下這些模式的區(qū)別抠刺。

1.餓漢模式:

public class SingleTon{
private static SingleTon INSTANCE = new SingleTon();
private SingleTon(){}
public static SingleTon getInstance(){ return INSTANCE; }}
餓漢模式在類(lèi)被初始化時(shí)就已經(jīng)在內(nèi)存中創(chuàng)建了對(duì)象,以空間換時(shí)間洛口,故不存在線程安全問(wèn)題矫付。

2.懶漢模式:

public class SingleTon{
private static SingleTon INSTANCE = null;
private SingleTon(){}
public static SingleTon getInstance() {
if(INSTANCE == null){
INSTANCE = new SingleTon();
}
return INSTANCE;
}
}
懶漢模式在方法被調(diào)用后才創(chuàng)建對(duì)象第焰,以時(shí)間換空間买优,在多線程環(huán)境下存在風(fēng)險(xiǎn)。

3.雙重鎖懶漢模式(Double Check Lock)

public class SingleTon{
private static SingleTon INSTANCE = null;
private SingleTon(){}
public static SingleTon getInstance(){if(INSTANCE == null){
synchronized(SingleTon.class){
if(INSTANCE == null){
INSTANCE = new SingleTon();
}
}
return INSTANCE;
}
}
}

DCL模式的優(yōu)點(diǎn)就是挺举,只有在對(duì)象需要被使用時(shí)才創(chuàng)建杀赢,第一次判斷 INSTANCE == null為了避免非必要加鎖,當(dāng)?shù)谝淮渭虞d時(shí)才對(duì)實(shí)例進(jìn)行加鎖再實(shí)例化湘纵。這樣既可以節(jié)約內(nèi)存空間脂崔,又可以保證線程安全。但是梧喷,由于jvm存在亂序執(zhí)行功能砌左,DCL也會(huì)出現(xiàn)線程不安全的情況脖咐。具體分析如下:

INSTANCE = new SingleTon();
這個(gè)步驟,其實(shí)在jvm里面的執(zhí)行分為三步:

      1.在堆內(nèi)存開(kāi)辟內(nèi)存空間汇歹。

2.在堆內(nèi)存中實(shí)例化SingleTon里面的各個(gè)參數(shù)屁擅。
3.把對(duì)象指向堆內(nèi)存空間。

由于jvm存在亂序執(zhí)行功能产弹,所以可能在2還沒(méi)執(zhí)行時(shí)就先執(zhí)行了3派歌,如果此時(shí)再被切換到線程B上,由于執(zhí)行了3痰哨,INSTANCE 已經(jīng)非空了胶果,會(huì)被直接拿出來(lái)用,這樣的話斤斧,就會(huì)出現(xiàn)異常早抠。這個(gè)就是著名的DCL失效問(wèn)題。

不過(guò)在JDK1.5之后折欠,官方也發(fā)現(xiàn)了這個(gè)問(wèn)題贝或,故而具體化了volatile吼过,即在JDK1.6及以后锐秦,只要定義為private volatile static SingleTon INSTANCE = null;就可解決DCL失效問(wèn)題。volatile確保INSTANCE每次均在主內(nèi)存中讀取盗忱,這樣雖然會(huì)犧牲一點(diǎn)效率酱床,但也無(wú)傷大雅。

3.靜態(tài)內(nèi)部類(lèi)模式:

public class SingleTon{
private SingleTon(){}

private static class SingleTonHoler{
private static SingleTon INSTANCE = new SingleTon();
}

public static SingleTon getInstance(){
return SingleTonHoler.INSTANCE;
}
}
靜態(tài)內(nèi)部類(lèi)的優(yōu)點(diǎn)是:外部類(lèi)加載時(shí)并不需要立即加載內(nèi)部類(lèi)趟佃,內(nèi)部類(lèi)不被加載則不去初始化INSTANCE扇谣,故而不占內(nèi)存。即當(dāng)SingleTon第一次被加載時(shí)闲昭,并不需要去加載SingleTonHoler罐寨,只有當(dāng)getInstance()方法第一次被調(diào)用時(shí),才會(huì)去初始化INSTANCE,第一次調(diào)用getInstance()方法會(huì)導(dǎo)致虛擬機(jī)加載SingleTonHoler類(lèi)序矩,這種方法不僅能確保線程安全鸯绿,也能保證單例的唯一性,同時(shí)也延遲了單例的實(shí)例化簸淀。

那么瓶蝴,靜態(tài)內(nèi)部類(lèi)又是如何實(shí)現(xiàn)線程安全的呢?首先租幕,我們先了解下類(lèi)的加載時(shí)機(jī)舷手。

類(lèi)加載時(shí)機(jī):JAVA虛擬機(jī)在有且僅有的5種場(chǎng)景下會(huì)對(duì)類(lèi)進(jìn)行初始化。
1.遇到new劲绪、getstatic男窟、setstatic或者invokestatic這4個(gè)字節(jié)碼指令時(shí)盆赤,對(duì)應(yīng)的java代碼場(chǎng)景為:new一個(gè)關(guān)鍵字或者一個(gè)實(shí)例化對(duì)象時(shí)、讀取或設(shè)置一個(gè)靜態(tài)字段時(shí)(final修飾歉眷、已在編譯期把結(jié)果放入常量池的除外)弟劲、調(diào)用一個(gè)類(lèi)的靜態(tài)方法時(shí)。
2.使用java.lang.reflect包的方法對(duì)類(lèi)進(jìn)行反射調(diào)用的時(shí)候姥芥,如果類(lèi)沒(méi)進(jìn)行初始化兔乞,需要先調(diào)用其初始化方法進(jìn)行初始化。
3.當(dāng)初始化一個(gè)類(lèi)時(shí)凉唐,如果其父類(lèi)還未進(jìn)行初始化庸追,會(huì)先觸發(fā)其父類(lèi)的初始化。
4.當(dāng)虛擬機(jī)啟動(dòng)時(shí)台囱,用戶(hù)需要指定一個(gè)要執(zhí)行的主類(lèi)(包含main()方法的類(lèi))淡溯,虛擬機(jī)會(huì)先初始化這個(gè)類(lèi)。
5.當(dāng)使用JDK 1.7等動(dòng)態(tài)語(yǔ)言支持時(shí)簿训,如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic咱娶、REF_putStatic、REF_invokeStatic的方法句柄强品,并且這個(gè)方法句柄所對(duì)應(yīng)的類(lèi)沒(méi)有進(jìn)行過(guò)初始化膘侮,則需要先觸發(fā)其初始化。
這5種情況被稱(chēng)為是類(lèi)的主動(dòng)引用的榛,注意琼了,這里《虛擬機(jī)規(guī)范》中使用的限定詞是"有且僅有",那么夫晌,除此之外的所有引用類(lèi)都不會(huì)對(duì)類(lèi)進(jìn)行初始化雕薪,稱(chēng)為被動(dòng)引用。靜態(tài)內(nèi)部類(lèi)就屬于被動(dòng)引用的行列晓淀。

我們?cè)倩仡^看下getInstance()方法所袁,調(diào)用的是SingleTonHoler.INSTANCE,取的是SingleTonHoler里的INSTANCE對(duì)象凶掰,跟上面那個(gè)DCL方法不同的是燥爷,getInstance()方法并沒(méi)有多次去new對(duì)象,故不管多少個(gè)線程去調(diào)用getInstance()方法锄俄,取的都是同一個(gè)INSTANCE對(duì)象局劲,而不用去重新創(chuàng)建。當(dāng)getInstance()方法被調(diào)用時(shí)奶赠,SingleTonHoler才在SingleTon的運(yùn)行時(shí)常量池里鱼填,把符號(hào)引用替換為直接引用,這時(shí)靜態(tài)對(duì)象INSTANCE也真正被創(chuàng)建毅戈,然后再被getInstance()方法返回出去苹丸,這點(diǎn)同餓漢模式愤惰。那么INSTANCE在創(chuàng)建過(guò)程中又是如何保證線程安全的呢?在《深入理解JAVA虛擬機(jī)》中赘理,有這么一句話:

虛擬機(jī)會(huì)保證一個(gè)類(lèi)的<clinit>()方法在多線程環(huán)境中被正確地加鎖宦言、同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類(lèi)商模,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類(lèi)的<clinit>()方法奠旺,其他線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行<clinit>()方法完畢施流。如果在一個(gè)類(lèi)的<clinit>()方法中有耗時(shí)很長(zhǎng)的操作响疚,就可能造成多個(gè)進(jìn)程阻塞(需要注意的是,其他線程雖然會(huì)被阻塞瞪醋,但如果執(zhí)行<clinit>()方法后忿晕,其他線程喚醒之后不會(huì)再次進(jìn)入<clinit>()方法。同一個(gè)加載器下银受,一個(gè)類(lèi)型只會(huì)初始化一次践盼。),在實(shí)際應(yīng)用中宾巍,這種阻塞往往是很隱蔽的咕幻。

故而,可以看出INSTANCE在創(chuàng)建過(guò)程中是線程安全的蜀漆,所以說(shuō)靜態(tài)內(nèi)部類(lèi)形式的單例可保證線程安全谅河,也能保證單例的唯一性咱旱,同時(shí)也延遲了單例的實(shí)例化确丢。

那么,是不是可以說(shuō)靜態(tài)內(nèi)部類(lèi)單例就是最完美的單例模式了呢吐限?其實(shí)不然鲜侥,靜態(tài)內(nèi)部類(lèi)也有著一個(gè)致命的缺點(diǎn),就是傳參的問(wèn)題诸典,由于是靜態(tài)內(nèi)部類(lèi)的形式去創(chuàng)建單例的描函,故外部無(wú)法傳遞參數(shù)進(jìn)去,例如Context這種參數(shù)狐粱,所以舀寓,我們創(chuàng)建單例時(shí),可以在靜態(tài)內(nèi)部類(lèi)與DCL模式里自己斟酌肌蜻。

最后粗略的介紹下枚舉類(lèi)型的單例吧互墓。

枚舉單例:

public enum SingleTon{
INSTANCE;
public void method(){
//TODO
}
}
枚舉在java中與普通類(lèi)一樣,都能擁有字段與方法蒋搜,而且枚舉實(shí)例創(chuàng)建是線程安全的篡撵,在任何情況下判莉,它都是一個(gè)單例。我們可直接以

SingleTon.INSTANCE
的方式調(diào)用育谬。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末券盅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子膛檀,更是在濱河造成了極大的恐慌锰镀,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咖刃,死亡現(xiàn)場(chǎng)離奇詭異互站,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)僵缺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)胡桃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人磕潮,你說(shuō)我怎么就攤上這事翠胰。” “怎么了自脯?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵之景,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我膏潮,道長(zhǎng)锻狗,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任焕参,我火速辦了婚禮轻纪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叠纷。我一直安慰自己刻帚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布涩嚣。 她就那樣靜靜地躺著崇众,像睡著了一般。 火紅的嫁衣襯著肌膚如雪航厚。 梳的紋絲不亂的頭發(fā)上顷歌,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音幔睬,去河邊找鬼眯漩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛溪窒,可吹牛的內(nèi)容都是我干的坤塞。 我是一名探鬼主播冯勉,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼摹芙!你這毒婦竟也來(lái)了灼狰?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤浮禾,失蹤者是張志新(化名)和其女友劉穎交胚,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體盈电,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝴簇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匆帚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熬词。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吸重,靈堂內(nèi)的尸體忽然破棺而出互拾,到底是詐尸還是另有隱情,我是刑警寧澤嚎幸,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布颜矿,位于F島的核電站,受9級(jí)特大地震影響嫉晶,放射性物質(zhì)發(fā)生泄漏骑疆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一替废、第九天 我趴在偏房一處隱蔽的房頂上張望箍铭。 院中可真熱鬧,春花似錦舶担、人聲如沸坡疼。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至闸氮,卻和暖如春剪况,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒲跨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工译断, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人或悲。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓孙咪,卻偏偏與公主長(zhǎng)得像堪唐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子翎蹈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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