單例模式

單例模式的定義與特點

老弟覺得叨襟,為了節(jié)省內(nèi)存資源续捂,保證數(shù)據(jù)內(nèi)容的全局一致性榨惰,要求某些類只能創(chuàng)建一個實例扯键,這就是所謂的單例模式坏怪。

單例(Singleton)模式的定義:指一個類只有一個實例简烘,且該類能自行創(chuàng)建這個實例的一種模式笑陈。

在計算機系統(tǒng)中放刨,還有 Windows 的回收站笛臣、操作系統(tǒng)中的文件系統(tǒng)云稚、多線程中的線程池、顯卡的驅(qū)動程序?qū)ο笊虮ぁ⒋蛴C的后臺處理服務静陈、應用程序的日志對象、數(shù)據(jù)庫的連接池、網(wǎng)站的計數(shù)器鲸拥、Web 應用的配置對象拐格、應用程序中的對話框、系統(tǒng)中的緩存等常常被設計成單例刑赶。

單例模式有 3 個特點:

1.單例類只有一個實例對象捏浊。

2.該單例對象必須由單例類自行創(chuàng)建。

3.單例類對外提供一個訪問該單例的全局訪問點角撞。

單例模式的結(jié)構(gòu)與實現(xiàn)

單例模式是設計模式中最簡單的模式之一呛伴。通常,普通類的構(gòu)造函數(shù)是公有的谒所,外部類可以通過“new 構(gòu)造函數(shù)()”來生成多個實例热康。但是,如果將類的構(gòu)造函數(shù)設為私有的劣领,外部類就無法調(diào)用該構(gòu)造函數(shù)姐军,也就無法生成多個實例。這時該類自身必須定義一個靜態(tài)私有實例尖淘,并向外提供一個靜態(tài)的公有函數(shù)用于創(chuàng)建或獲取該靜態(tài)私有實例奕锌。

1. 單例模式的結(jié)構(gòu)

單例模式的主要角色如下

單例類:包含一個實例且能自行創(chuàng)建這個實例的類。

訪問類:使用單例的類村生。

單例模式有很多種實現(xiàn)的方式惊暴,例如:懶漢式、惡漢式趁桃、靜態(tài)內(nèi)部類模式和枚舉辽话。但是懶漢式和餓漢式通常不作為我們的技術方案,通常會使用靜態(tài)內(nèi)部類的模式來構(gòu)建單例卫病。

2. 單例模式的實現(xiàn)

2.1 懶漢式


public class LazySingletonUnsafe01 {

    private static LazySingletonUnsafe01 INSTANCE;

    private LazySingletonUnsafe01(){}

    public static LazySingletonUnsafe01 getInstance(){

        if ( null == INSTANCE ){                   //非原子操作 油啤,線程不安全

            // tmp line 01

            INSTANCE = new LazySingletonUnsafe01();

        }

        return INSTANCE;

    }

}

懶漢式的單例模式是在類初始化時先不對靜態(tài)變量 INSTANCE 初始化,該類的構(gòu)造器為私有蟀苛,故而獲得該類實例只能通過getInstance方法益咬,在獲得該類實例前判斷是否為空,若為空則創(chuàng)建該實例并返回帜平。代碼看起來沒有什么問題幽告,但是常言道Java天生就是多線程的,不支持并發(fā)的程序不是好碼農(nóng)罕模,這個程序并不是線程安全的评腺。例如,敏感詞表淑掌,如果兩個線程同時執(zhí)行該方法并且同時運行到 “tmp line 01” 蒿讥,就一定會創(chuàng)建兩個實例對象。這違背了單例模式的意義。所以芋绸,在java程序中為了保證線程安全媒殉,要在方法上加上synchronized。當然你也可是使用Lock的實現(xiàn)類ReentranLock來作為synchronized的替代摔敛。


public class LazySingletonSafe01 {

    private  static LazySingletonSafe01 INSTANCE;

    private LazySingletonSafe01(){}

    public static synchronized LazySingletonSafe01 getInstance(){

        if ( null == INSTANCE ){

            INSTANCE = new LazySingletonSafe01();

        }

        return INSTANCE;

    }

}

但是廷蓉,該方案的弊端就是,想要獲得實例就必須先獲取對象的鎖(這里是靜態(tài)方法所以指的是類對象)马昙。程序中如果多處用到該實例桃犬,類似操作的線程都需要申請這個鎖,難免會發(fā)生阻塞行楞,效率低下攒暇。所以就衍生出來雙重校驗的模式。


public class LazySingletonSafe02 {

    private volatile static LazySingletonSafe02 INSTANCE;

    private LazySingletonSafe02(){}

    public static LazySingletonSafe02 getInstance(){//雙重驗證保證安全

        if ( null == INSTANCE ){

            synchronized (LazySingletonSafe02.class){

                if ( null == INSTANCE ){

                    // tmp line 01

                    INSTANCE = new LazySingletonSafe02();

                }

            }

        }

        return INSTANCE;

    }

}

所謂的雙重校驗子房,在該方法的層面只是進行了二次判斷形用,在創(chuàng)建實例后其他線程不再需要獲取鎖來得到實例。雙重校驗的關鍵在于“volatile”证杭,僅僅使用sychronized并不能解決JVM指令重排序的問題田度。

Java單例模式為什么要加volatile?點擊查看

2.2 餓漢式


public class HungrySingleton01 {

    private final static HungrySingleton01 INSTANCE = new HungrySingleton01();

    private HungrySingleton01(){}

    public static HungrySingleton01 getInstance(){

        return INSTANCE;

    }

}

餓漢式在靜態(tài)變量初始化時賦初值解愤。


public class HungrySingleton02 {

    private static HungrySingleton02 INSTANCE ;

    static {

        INSTANCE = new HungrySingleton02();

    }

    private HungrySingleton02(){}

    public static HungrySingleton02 getInstance(){

        return INSTANCE;

    }

}

也可以寫成在靜態(tài)初始化塊中初始化靜態(tài)變量镇饺。

2.3 靜態(tài)內(nèi)部類

public class DefaultLabelVocabularyDictionary {

    // 日志對象

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultLabelVocabularyDictionary.class);

    // 字典容器

    private Map<String, String> dictionary;

    // 私有構(gòu)造器

    private DefaultLabelVocabularyDictionary() {

        // mock

        dictionary = Collections.UnmodifiableMap(dict);

    }

    // 靜態(tài)內(nèi)部類,聲明外部類對象送讲,靜態(tài)初始化塊初始化INSTANCE實例兰怠,確保對象唯一。

    private static class Holder {

        private static final DefaultLabelVocabularyDictionary INSTANCE;

        static {

            try {

                LOGGER.info("To load Default Label Vocabulary Dictionary from path:{}", DICT_FILE_PATH);

                INSTANCE = new DefaultLabelVocabularyDictionary();

            } catch (Exception e) {

                // 此處可以自定義Exception李茫,方便在日志中找到字典初始化異常的信息

                throw new IllegalStateException("Loading Default Label Vocabulary Dictionary fail!", e);

            }

        }

    }

    public static boolean containsKey(String key) {

        return Holder.INSTANCE.dictionary.containsKey(key);

    }

    public static Map<String, String> getInstance() {

        return Holder.INSTANCE.dictionary;

    }

}

老弟在實際編碼中使用到的靜態(tài)內(nèi)部類創(chuàng)建單例模式的字典的代碼,對外暴露的只有功能性的方法肥橙,且Collections.UnmodiableMap()方法會把Map包裝成一個不可更改的對象魄宏。

2.4 枚舉

使用枚舉類型能保證枚舉類中的每個對象在全局都是單例的,但是一般枚舉類作為全局的異常存筏、code宠互、基礎類型等,目前在在下的碼歷中并未發(fā)現(xiàn)有使用枚舉做單例的代碼椭坚。但是實現(xiàn)起來很簡單予跌,此處也就不書寫示例代碼了。

3. 單例模式的應用場景

前面分析了單例模式的結(jié)構(gòu)與特點善茎,以下是它通常適用的場景的特點券册。

在應用場景中,某類只要求生成一個對象的時候。

當對象需要被共享的場合烁焙。由于單例模式只允許創(chuàng)建一個對象航邢,共享該對象可以節(jié)省內(nèi)存,并加快對象訪問速度骄蝇。

當某類需要頻繁實例化膳殷,而創(chuàng)建的對象又頻繁被銷毀的時候。

4. 單例模式的擴展

單例模式可擴展為有限的多例(Multitcm)模式九火,這種模式可生成有限個實例并保存在 ArmyList 中赚窃,客戶需要時可隨機獲取。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末岔激,一起剝皮案震驚了整個濱河市勒极,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鹦倚,老刑警劉巖河质,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異震叙,居然都是意外死亡掀鹅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門媒楼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乐尊,“玉大人,你說我怎么就攤上這事划址∪忧叮” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵夺颤,是天一觀的道長痢缎。 經(jīng)常有香客問我,道長世澜,這世上最難降的妖魔是什么独旷? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮寥裂,結(jié)果婚禮上嵌洼,老公的妹妹穿的比我還像新娘。我一直安慰自己封恰,他們只是感情好麻养,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著诺舔,像睡著了一般鳖昌。 火紅的嫁衣襯著肌膚如雪备畦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天遗遵,我揣著相機與錄音萍恕,去河邊找鬼。 笑死车要,一個胖子當著我的面吹牛允粤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翼岁,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼类垫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了琅坡?” 一聲冷哼從身側(cè)響起悉患,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎榆俺,沒想到半個月后售躁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡茴晋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年陪捷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诺擅。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡市袖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出烁涌,到底是詐尸還是另有隱情苍碟,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布撮执,位于F島的核電站微峰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏抒钱。R本人自食惡果不足惜县忌,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望继效。 院中可真熱鬧,春花似錦装获、人聲如沸瑞信。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凡简。三九已至逼友,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秤涩,已是汗流浹背帜乞。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留筐眷,地道東北人黎烈。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像匀谣,于是被迫代替她去往敵國和親照棋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350