教你一步步寫完美的單例模式

之前只會寫固定的單例模式难述,沒有仔細(xì)研究過。最佳在書上看到介紹一步步單例模式吐句。不過是用cpp寫的胁后,與是自己用java一步步實現(xiàn)一遍。

Step1 適應(yīng)于單線程的Singleton

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

我們將構(gòu)造方法設(shè)為private避免了類在外部被實例化嗦枢,這樣在同一個虛擬機(jī)范圍內(nèi)攀芯,Singleton的唯一實例只能通過getInstance()方法訪問。并且我們的方法和成員變量都是靜態(tài)的文虏。

不足

這種方法如果在單線程中使用是能夠正常運(yùn)行的侣诺,但是如果我們是在多線程情況下呢?想一想氧秘,如果有兩個或以上的同學(xué)運(yùn)行到判斷instance是否為null的if語句的時候年鸳,這個時候如果instance沒有創(chuàng)建,那么這些線程都會創(chuàng)建instance丸相。這是就不滿足我們的單例要求了搔确。

Step2 在多線程下的Singleton

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

不細(xì)心的你可能會說這兩行代碼不是一樣的嗎。不對灭忠,我們對getInstance方法實現(xiàn)了同步鎖膳算。此時如果有多個線程想創(chuàng)建一個實例,因為在同一時刻只能由一個線程得到同步鎖弛作,當(dāng)?shù)谝粋€線程加上鎖時涕蜂,后面的線程就需要等待。當(dāng)?shù)谝粋€線程發(fā)現(xiàn)instance還沒有創(chuàng)建的時候就會去創(chuàng)建映琳。接著第一個線程釋放同步鎖机隙,后面的線程加上同步鎖蜘拉,這時候?qū)嵗呀?jīng)由第一個線程創(chuàng)建了,所有第二個線程就不會重復(fù)的去創(chuàng)建了有鹿。

不足

雖然我們實現(xiàn)了多線程環(huán)境下的單例旭旭,但是你會發(fā)現(xiàn)我們每次在通過getInstance獲取實例時,我們都視圖去加上一個鎖印颤,但是加鎖是一個耗時操作您机,我們應(yīng)該盡量避免它穿肄。

Step3 避免加鎖帶來的耗時

我們只需要當(dāng)我們的實例還沒有創(chuàng)建的時候進(jìn)行加鎖防止多個線程同時創(chuàng)建實例年局,當(dāng)實例已經(jīng)創(chuàng)建的時候我們就應(yīng)該避免加鎖。

public class Singleton {
    private Singleton() {}
    
    private volatile static Singleton INSTANCE = null;//使用volatile修飾禁止java重排序
    
    public static  Singleton getInstance(){
        if (INSTANCE==null) {
            synchronized (Singleton.class){
                if (INSTANCE==null) {   //二次檢測
                    INSTANCE=new Singleton();   
                }
            }
        }
        return INSTANCE;
    }
}

我們在加鎖前進(jìn)行一次判斷咸产,這樣只有當(dāng)instance不存在的時候才需要加鎖操作矢否。這樣我們的單例寫的比較完美了,但是if語句的判斷容易增加我們代碼的錯誤率脑溢,精益求精的我們肯定不止步于此僵朗,再來想想更加優(yōu)秀的解法。

一開始我是沒有使用volatile修飾符的屑彻,這樣可能出現(xiàn)一個問題验庙,在另一個線一中看到以個初始化了一半的instance的情況,但使用了volatile變量后社牲,就能保證先行發(fā)生關(guān)系(happens-before relationship)粪薛。對于volatile變量_instance,所有的寫(write)都將先行發(fā)生于讀(read)搏恤,在Java 5之前不是這樣违寿,所以在這之前使用雙重檢查鎖有問題。有了先行發(fā)生的保障(happens-before guarantee)熟空,可以認(rèn)為它是安全的

推薦解法一

public class Singleton {
    private Singleton() {}
    
    public static final Singleton getInstance(){
        return MyInstance.INSTANCE;
    }
    
    private static class MyInstance{
        private static final Singleton INSTANCE=new Singleton();
    }
}

對了藤巢,上沒的幾種方法和解法一都是我們常說的懶漢模式。懶漢模式實現(xiàn)的是按需加載的單例模式息罗。只有當(dāng)我們需要的時候調(diào)用getInstance方法才會實例化這個單例掂咒。

推薦解法二

public class Singleton{
    private Singleton() {}
    private static final Singleton INSTANCE=new Singleton();

    public static final Singleton getInstance(){
        return INSTANCE;
    }
}

上面這種就是餓漢模式了,餓漢就是類一旦加載迈喉,就把單例初始化完成俏扩,保證getInstance的時候,需要的實例是已經(jīng)存在的了弊添。因為餓漢模式在類創(chuàng)建的同時就已經(jīng)創(chuàng)建好一個靜態(tài)的對象供系統(tǒng)使用录淡,以后不再改變,所以天生是線程安全的油坝。

推薦解法三

public enum Singleton{
    INSTANCE嫉戚;
}

除過枚舉實現(xiàn)的單例模式以外的其他實現(xiàn)方式都有一個比較大的問題是一旦實現(xiàn)了 Serializable 接口后就不再是單例了刨裆,因為每次調(diào)用 readObject() 方法返回的都是一個新創(chuàng)建出來的對象(當(dāng)然可以通過使用 readResolve() 方法來避免,但是終歸麻煩)彬檀,而 Java 規(guī)范中保證了每一個枚舉類型及其定義的枚舉變量在 JVM 中都是唯一的帆啃,在枚舉類型的序列化和反序列化上 Java 做了特殊處理,序列化時 Java 僅僅是將枚舉對象的 name 屬性輸出到結(jié)果中窍帝,反序列化時則是通過 java.lang.Enum 的 valueOf 方法來根據(jù)名字查找枚舉對象努潘,同時禁用了 writeObject、readObject坤学、readObjectNoData疯坤、writeReplace 和 readResolve 等方法。參考這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末深浮,一起剝皮案震驚了整個濱河市压怠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌飞苇,老刑警劉巖菌瘫,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異布卡,居然都是意外死亡雨让,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門忿等,熙熙樓的掌柜王于貴愁眉苦臉地迎上來栖忠,“玉大人,你說我怎么就攤上這事这弧⊥尴校” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵匾浪,是天一觀的道長皇帮。 經(jīng)常有香客問我,道長蛋辈,這世上最難降的妖魔是什么属拾? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮冷溶,結(jié)果婚禮上渐白,老公的妹妹穿的比我還像新娘。我一直安慰自己逞频,他們只是感情好纯衍,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著苗胀,像睡著了一般襟诸。 火紅的嫁衣襯著肌膚如雪瓦堵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天歌亲,我揣著相機(jī)與錄音菇用,去河邊找鬼。 笑死陷揪,一個胖子當(dāng)著我的面吹牛惋鸥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播悍缠,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼卦绣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扮休?” 一聲冷哼從身側(cè)響起迎卤,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤拴鸵,失蹤者是張志新(化名)和其女友劉穎玷坠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體劲藐,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡八堡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了聘芜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兄渺。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖汰现,靈堂內(nèi)的尸體忽然破棺而出挂谍,到底是詐尸還是另有隱情,我是刑警寧澤瞎饲,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布口叙,位于F島的核電站,受9級特大地震影響嗅战,放射性物質(zhì)發(fā)生泄漏妄田。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一驮捍、第九天 我趴在偏房一處隱蔽的房頂上張望疟呐。 院中可真熱鬧,春花似錦东且、人聲如沸启具。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鲁冯。三九已至囤踩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間晓褪,已是汗流浹背堵漱。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留涣仿,地道東北人勤庐。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像好港,于是被迫代替她去往敵國和親愉镰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

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