單例模式

單例模式是在一個應(yīng)用(Application)中某個類(Class)有且僅有一個實例(Instance)存在。

相當(dāng)于一個全局對象弓熏,方便對整個系統(tǒng)的行為進行協(xié)調(diào)恋谭,比如服務(wù)器的配置信息由一個單例對象保存。

單例模式又可細分為 懶漢方式(lazy-initialization)和餓漢方式挽鞠,懶漢方式是只有需要的時候才會去創(chuàng)建疚颊,餓漢方式則是在Application初始化或者.class類文件加載階段直接創(chuàng)建。

餓漢模式(不存在線程不安全的問題):

public class Singleton {
    private final static Singleton INSTANCE = new Singleton();
    // Private constructor suppresses 
    private Singleton() {}
    // default public constructor
    public final static Singleton getInstance() {
        return INSTANCE;
    }
}

final staticINSTANCE只有在Singleton的成員變量或者函數(shù)(非final static literal成員變量)第一次被使用的時候才會觸發(fā)實例化信认。在Singleton類加載到JVM的時候INSTANCE被放到JVM方法區(qū)常量池中材义,其值new Singleton()則只是存了Singleton類以及constructor方法的符號引用,實例化被JVM推遲嫁赏。

一般的懶漢模式(非線程安全):

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

試想在單核模式下當(dāng)兩個線程t1和t2一同訪問getInstance()函數(shù)母截,t1先檢查INSTANCE是否是null之后被掛起,t2檢查INSTANCEnull之后創(chuàng)建Singleton的instance并掛起橄教,t1被喚醒再次創(chuàng)建Singleton的instance清寇,造成了數(shù)據(jù)的覆蓋。

一般的懶漢模式(線程安全):

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

直接給getInstance()Class級別函數(shù)加互斥鎖护蝶,每個訪問該函數(shù)的線程都得先拿到該Class的monitor鎖华烟。但是這樣過多的加鎖解鎖消耗資源不夠efficiency。

Double Check的懶漢模式(線程安全):

public class Singleton {
    private static volatile Singleton INSTANCE = null;
    // Private constructor suppresses
    private Singleton() {}
    //thread safe and performance  promote
    public static  Singleton getInstance() {
        if(INSTANCE == null){
            synchronized(Singleton.class){
                if(INSTANCE == null){
                    INSTANCE = new Singleton();
                  }
              }
        }
        return INSTANCE;
    }
}

相比前一個實現(xiàn)持灰,每次getInstance都先檢查是否是null盔夜,如果不是null則不執(zhí)行加鎖解鎖的操作,所以更高效堤魁。

volatile關(guān)鍵字的作用是使變量內(nèi)存可見(visible to memory)喂链,防止代碼重排序(確保happens-before原則)。

synchronized關(guān)鍵字則會為Singleton.class加monitor鎖妥泉,每個調(diào)用該段代碼的Thread必須先拿到Singleton.class的monitor鎖才能執(zhí)行該段代碼椭微,而同一時間只能有一個Thread持有該monitor鎖。

靜態(tài)內(nèi)部類的實現(xiàn):

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

內(nèi)部類StingletonHandler在編譯之后會生成一個單獨的.class文件:Singleton$SingletonHandler.class盲链。Singleton.class被加載方法區(qū)之后保留的只是SingletonHandlerSingletonHandler.INSTANCE的qualified name(全限定名的符號引用symbolic reference)蝇率,只有在調(diào)用getInstance方法時JVM才會通過classloader將Singleton$SingletonHandler.class加載到方法區(qū)迟杂,在heap區(qū)創(chuàng)建Singleton的實例,并將符號引用(symbolic reference)轉(zhuǎn)換為直接引用(direct reference)本慕。
以上加載過程都是線程安全的排拷,所以該實現(xiàn)高效&自動線程安全。

使用場景:當(dāng)Singleton這個類有提供其他很多static的函數(shù)的話锅尘,通過SingletonHandler可以實現(xiàn)懶加載(僅在需要用到這個instance的時候才去加載內(nèi)部類并創(chuàng)建Singleton的instance)监氢。

Enum實現(xiàn):

public Enum Singleton{
    INSTANCE;
    //functions to add
}

Decompile之后的代碼是:

public final class Singleton extends Enum
{
    private Singleton(String s, int i)
    {
        super(s, i);
    }

    public static Singleton[] values()
    {
        Singleton asingleton[];
        int i;
        Singleton asingleton1[];
        System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
        return asingleton1;
    }

    public static Singleton valueOf(String s)
    {
        return (Singleton)Enum.valueOf(structure/proxy/Singleton, s);
    }

    public static final Singleton INSTANCE;
    private static final Singleton ENUM$VALUES[];

    static
    {
        INSTANCE = new Singleton("INSTANCE", 0);
        ENUM$VALUES = (new Singleton[] {
            INSTANCE
        });
    }
}

和餓漢模式很像,相比而言優(yōu)點是寫法更簡單藤违,而且自動提供了Serialisable序列化(其他單例實現(xiàn)的序列化則需手動implements Serialisable接口實現(xiàn)序列化)忙菠。

關(guān)于JVM類加載流程在這里先簡單寫一下:

  1. loading -> 找.class文件(TYPE)并加載入JVM
  2. linking -> 分三部分
    2.1. verification -> 檢查引入TYPE文件正確性
    2.2. preparation -> 給class變量分配內(nèi)存(在方法區(qū))并賦值default value:(boolean:false, int:0, reference:null)
    2.3. resolution -> 將symbolic reference轉(zhuǎn)為direct reference (通常延后觸發(fā))
  3. initialization -> 觸發(fā)代碼提供的賦值語句(通常延后觸發(fā))

歡迎批評指正,謝謝纺弊!

Reference

https://zh.wikipedia.org/wiki/單例模式
https://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java
https://stackoverflow.com/questions/16771373/singleton-via-enum-way-is-lazy-initialized

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末牛欢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子淆游,更是在濱河造成了極大的恐慌傍睹,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件犹菱,死亡現(xiàn)場離奇詭異拾稳,居然都是意外死亡,警方通過查閱死者的電腦和手機腊脱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門访得,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人陕凹,你說我怎么就攤上這事悍抑。” “怎么了杜耙?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵搜骡,是天一觀的道長。 經(jīng)常有香客問我佑女,道長记靡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任团驱,我火速辦了婚禮摸吠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嚎花。我一直安慰自己寸痢,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布贩幻。 她就那樣靜靜地躺著轿腺,像睡著了一般两嘴。 火紅的嫁衣襯著肌膚如雪丛楚。 梳的紋絲不亂的頭發(fā)上族壳,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機與錄音趣些,去河邊找鬼仿荆。 笑死,一個胖子當(dāng)著我的面吹牛坏平,可吹牛的內(nèi)容都是我干的拢操。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼舶替,長吁一口氣:“原來是場噩夢啊……” “哼令境!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起顾瞪,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤舔庶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后陈醒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惕橙,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年钉跷,在試婚紗的時候發(fā)現(xiàn)自己被綠了弥鹦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡爷辙,死狀恐怖彬坏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膝晾,我是刑警寧澤苍鲜,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站玷犹,受9級特大地震影響混滔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜歹颓,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一坯屿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧巍扛,春花似錦领跛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喊括。三九已至,卻和暖如春矢棚,著一層夾襖步出監(jiān)牢的瞬間郑什,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工蒲肋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蘑拯,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓兜粘,卻偏偏與公主長得像申窘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子孔轴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348