你真的了解單例嗎

又到了一個老生常談的話題寸士,單例模式渤愁,可能在面試時我們也經(jīng)常會遇到牵祟,但是看似很簡單的問題,卻能看出一個人對單例理解的深度猴伶。要寫一個單例,首先需要讓構(gòu)造器私有塌西,還需要對外提供一個可以獲取單例的一個入口他挎,通常我們可能會這樣寫:

第一種:

public class SingleTon {

private static SingleTon instance = new SingleTon();

private SingleTon(){}

public static SingleTon get(){
    return instance;
}

}
這種方式簡單直接,實(shí)例隨著類加載而加載捡需,很方便办桨,但是卻不友好,有時候我們雖然加載了類站辉,卻沒有使用該類實(shí)例的時候呢撞,會造成內(nèi)存的浪費(fèi),不能達(dá)到懶加載的能力饰剥。所以我們可以改進(jìn)成下面這樣:

第二種:

public class SingleTon {

private static SingleTon instance = null;

private SingleTon(){}

public static SingleTon get(){
    if (instance == null){
        instance =new SingleTon();
    }
    return instance;
}

}
這樣可以達(dá)到懶加載殊霞,需要的時候在初始化,但是如果在多線程的情況下是不完全的汰蓉,那我們會這樣寫:

public class SingleTon {

private static SingleTon instance = null;

private SingleTon(){}

public static synchronized SingleTon get(){
    if (instance == null){
        instance =new SingleTon();
    }
    return instance;
}

}
雖然這樣安全了绷蹲,但是鎖的粒度還是比較大,所以為了減小鎖的粒度我們還會這樣寫:

public class SingleTon {

private static SingleTon instance = null;

private SingleTon() {
}

public static SingleTon get() {
    if (instance == null) {
        synchronized (SingleTon.class) {
            if (instance == null) {
                instance = new SingleTon();
            }
        }
    }
    return instance;
}

}
如果我們寫到這里就以為很滿足了顾孽,那么我只能說太天真了祝钢,看似一切完美,但是我們還是要問自己若厚,這樣就絕對的會線程安全嗎拦英?
要回答這個問題,這就不得不說一說對象的創(chuàng)建過程和java虛擬機(jī)的無序性测秸。首先在我們new對象的時候疤估,首先需要在方法區(qū)中去尋找該類的符號引用灾常,如果找不到,說明類還沒有被加載進(jìn)虛擬機(jī)做裙,所以需要通過類加載器先裝載該類岗憋,通過加載,驗(yàn)證锚贱,準(zhǔn)備仔戈,解析,初始化等操作拧廊,然后為對象在堆上開辟內(nèi)存空間(1)监徘,對象初始化操作(2),然后在將棧上的引用指向該對象內(nèi)存地址(3)吧碾。重點(diǎn)就在這凰盔,由于虛擬機(jī)的無序性,可能會造成執(zhí)行的順序并不是按照123進(jìn)行的倦春,也可能是按照132的執(zhí)行順序户敬,結(jié)果就是引用先指向?qū)ο蟮刂罚缓髮ο笤谶M(jìn)行初始化等操作睁本,這是由于線程的可見性造成的尿庐,所以為了保證變量instance的線程之間的可見性,我們需要將instance變量進(jìn)行volatile修飾來解決instance的可見性問題呢堰。(關(guān)于java虛擬機(jī)的無序性和volatile的內(nèi)存語義抄瑟,涉及到了java內(nèi)存模型的層面,這里暫時不過多分析枉疼,后面會單獨(dú)進(jìn)行講解)皮假。

所以正確的寫法是這樣:

public class SingleTon {

private static volatile SingleTon instance = null;

private SingleTon() {
}

public static SingleTon get() {
    if (instance == null) {
        synchronized (SingleTon.class) {
            if (instance == null) {
                instance = new SingleTon();
            }
        }
    }
    return instance;
}

}
我們還可以改成成一個類,專門生成單例:

public abstract class Singleton<T> {
private volatile T mInstance;

protected abstract T create();

public final T get() {
    synchronized (this) {
        if (mInstance == null) {
            mInstance = create();
        }
        return mInstance;
    }
}

}
那么到此我們就可以滿足了嗎骂维?當(dāng)然不能惹资。

這里不可避免的需要對volatile進(jìn)行解釋一下了,volatile在《深入理解java虛擬機(jī)》中有一下幾層含義:

1航闺,被volatile修飾的變量布轿,保證了該變量對其他線程的可見性,;

2来颤,禁止指令重排序汰扭,虛擬機(jī)會通過插入很多讀寫內(nèi)存屏障,來保證處理器不會亂序執(zhí)行福铅,但是也會造成編譯器不會對代碼進(jìn)行優(yōu)化(java內(nèi)存模型會最大限度的保證程序并行執(zhí)行)萝毛,對效率有一定影響。

那么我們在不使用volatile的前提下如何優(yōu)化呢滑黔,下面給出某大牛的寫法:

public class SingleTon {

private static SingleTon instance = null;

private SingleTon() {
}

public static SingleTon get() {
    if (instance == null) {
        synchronized (SingleTon.class) {
            if (instance == null) {
                SingleTon temp = null;
                try {
                    temp = new SingleTon();
                } catch (Exception e) {

                }
                if (temp != null)
                    instance = temp;
        }
    }
    return instance;
}

}
看似無用的代碼卻大有用處笆包,try的存在虛擬機(jī)無法優(yōu)化temp是否為空环揽,instance在賦值之前保證了對象已經(jīng)初始化完成♀钟叮看到這里明顯感覺到水很深啊歉胶。

   前面其實(shí)大概分為兩種,餓漢式和懶漢式巴粪,那有沒有既線程安全寫法簡單通今,又能懶加載呢?

第三種:

public class SingleTon {

private SingleTon() {
}

private static class SingleHolder {
    private static SingleTon instance = new SingleTon();
}

public static SingleTon get() {
    return SingleHolder.instance;
}

}
這里我們通過靜態(tài)內(nèi)部類來完成肛根,是不是很妙掸茅,我們無需枷鎖鄙麦,外部類的加載不會造成內(nèi)部類同時加載的,只有調(diào)用了get方法時才會加載內(nèi)部類筋夏,創(chuàng)建對象透葛,集前兩種方法的優(yōu)點(diǎn)于一身秽梅。

但是到這里我們又要分析了颇玷,以上寫法到底完全不锌云,如果通過反射或者反序列化還能保證是單例嗎?

當(dāng)然不可能褂乍,在反射面前持隧,一切都是小兒科了,這種寫法可阻止不了反射树叽,反序列化也不行舆蝴,你必須重寫readReslove方法谦絮,返回當(dāng)前實(shí)例题诵,不然就是多個實(shí)例了,

那到底有沒有絕對安全的單例啊层皱,我們是不是都快絕望了性锭,別急,放大招:

public enum SingleTon {
intstance;
}
是不是有點(diǎn)意外了叫胖,居然最簡單最安全的是枚舉草冈,至于枚舉是如何做到反射和反序列化時依然安全的可以看鏈接:

https://blog.csdn.net/gavin_dyson/article/details/70832185

好了,單例到此介紹完畢瓮增,看完這些你對單例模式真的了解了嗎怎棱?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市绷跑,隨后出現(xiàn)的幾起案子拳恋,更是在濱河造成了極大的恐慌,老刑警劉巖砸捏,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谬运,死亡現(xiàn)場離奇詭異隙赁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)梆暖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門伞访,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人轰驳,你說我怎么就攤上這事厚掷。” “怎么了滑废?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵蝗肪,是天一觀的道長。 經(jīng)常有香客問我蠕趁,道長薛闪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任俺陋,我火速辦了婚禮豁延,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘腊状。我一直安慰自己诱咏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布缴挖。 她就那樣靜靜地躺著袋狞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪映屋。 梳的紋絲不亂的頭發(fā)上苟鸯,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機(jī)與錄音棚点,去河邊找鬼早处。 笑死,一個胖子當(dāng)著我的面吹牛瘫析,可吹牛的內(nèi)容都是我干的砌梆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼贬循,長吁一口氣:“原來是場噩夢啊……” “哼咸包!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起杖虾,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤烂瘫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后亏掀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忱反,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泛释,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了温算。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怜校。...
    茶點(diǎn)故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖注竿,靈堂內(nèi)的尸體忽然破棺而出茄茁,到底是詐尸還是另有隱情,我是刑警寧澤巩割,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布裙顽,位于F島的核電站,受9級特大地震影響宣谈,放射性物質(zhì)發(fā)生泄漏愈犹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一闻丑、第九天 我趴在偏房一處隱蔽的房頂上張望漩怎。 院中可真熱鬧,春花似錦嗦嗡、人聲如沸勋锤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叁执。三九已至,卻和暖如春矮冬,著一層夾襖步出監(jiān)牢的瞬間谈宛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工欢伏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留入挣,地道東北人亿乳。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓硝拧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親葛假。 傳聞我的和親對象是個殘疾皇子障陶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評論 2 359

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

  • 最新在閱讀《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》一書抱究,我覺得寫的很清晰,每一個知識點(diǎn)都有示例带斑,通過示例更加容易理...
    慕涵盛華閱讀 549評論 0 3
  • 前言 本文主要參考 那些年鼓寺,我們一起寫過的“單例模式”勋拟。 何為單例模式? 顧名思義妈候,單例模式就是保證一個類僅有一個...
    tandeneck閱讀 2,517評論 1 8
  • 從三月份找實(shí)習(xí)到現(xiàn)在敢靡,面了一些公司,掛了不少苦银,但最終還是拿到小米啸胧、百度、阿里幔虏、京東纺念、新浪、CVTE想括、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,274評論 11 349
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法陷谱,類相關(guān)的語法,內(nèi)部類的語法瑟蜈,繼承相關(guān)的語法叭首,異常的語法,線程的語...
    子非魚_t_閱讀 31,661評論 18 399
  • 無意間見一個群里討論《每一天》這部電影踪栋,出于好奇心焙格,窩在國慶節(jié)期間依靠一些零碎時間,看完這部電影夷都,它是...
    慧姐學(xué)營養(yǎng)閱讀 487評論 2 4