單例模式

單例模式的定義

確保某一個類只有一個實(shí)例斤斧,而且自行實(shí)例化并向整個系統(tǒng)提供這個實(shí)例早抠。

單例模式的使用場景

確保某個類有且只有一個對象的場景,避免產(chǎn)生多個對象消耗過多的資源撬讽。例如創(chuàng)建一個對象需要消耗的資源過多蕊连,如要訪問 IO 和數(shù)據(jù)庫等資源悬垃。

image.png

(1)Client——高層客戶端;
(2)Singleton——單例類甘苍;

實(shí)現(xiàn)單例模式主要有如下幾個關(guān)鍵點(diǎn):
(1)構(gòu)造函數(shù)不對外開放尝蠕,一般為 Private;
(2)通過一個靜態(tài)方法或者枚舉返回單例類對象载庭;
(3)確保單例類的對象有且只有一個看彼,尤其是在多線程環(huán)境下;
(4)確保單例類對象在反序列化時不會重新構(gòu)建對象囚聚。

單例模式的簡單實(shí)例

(1)懶漢模式:

public class Singleton {

    private static Singleton sInstance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (sInstance == null) {
            sInstance = new Singleton();
        }
        return sInstance;
    }
}

getInstance() 方法中添加了 synchronized 關(guān)鍵字靖榕,保證方法在多線程情況下單例對象的唯一性。

懶漢單例模式的優(yōu)點(diǎn)是單例只有在使用時才會被實(shí)例化顽铸,在一定程度下節(jié)約了資源茁计;缺點(diǎn)是第一次加載時需要及時進(jìn)行實(shí)例化,反應(yīng)稍慢谓松,最大的問題是每次調(diào)用 getInstance 都進(jìn)行同步星压,造成不必要的同步開銷。一般不建議使用毒返。

(2)Double Check Lock (DCL)實(shí)現(xiàn)單例

public class Singleton {

    private static Singleton sInstance = null;

    private Singleton() {}

    public void doSomething() {
        System.out.println("do sth.");
    }

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

getInstance 方法中對 sIntance 進(jìn)行了兩次判空:

  • 第一層避免了不必要的同步租幕;
  • 第二層是為了在 null 的情況下創(chuàng)建實(shí)例。

假設(shè)線程 A 執(zhí)行到了 sInstance = new Singleton()語句拧簸,這里看起來是一句代碼劲绪,但實(shí)際上它并不是一個原子操作,這句代碼最終會被編譯成多條匯編指令盆赤,它大致做了 3 件事:

(1)給 Singleton 的實(shí)例分配內(nèi)存贾富;
(2)調(diào)用 Singleton 的構(gòu)造函數(shù),初始化成員字段牺六;
(3)將 sInstance 對象指向分配的內(nèi)存空間(此時 sInstance 就是不 null )

但是颤枪,由于 Java 編譯器允許處理器亂序執(zhí)行,以及 JDK1.5之前 JMM(Java Memory Model淑际,即內(nèi)存模型)中 Cache畏纲、寄存器到主內(nèi)存回寫順序的規(guī)定,上面的第二和第三執(zhí)行順序是無法保證的春缕,可能是 1-2-3盗胀,也可能是1-3-2。如果是后者锄贼,3 執(zhí)行完畢票灰,2 未執(zhí)行之前,被切換到了線程 B,這時候 sInstance 以及不為 null屑迂,所以線程 B 會直接取走 sInstance浸策,又因?yàn)闆]有執(zhí)行 2 所以成員字段沒有初始化,再使用時就會出錯惹盼,這就是 DCL 失效問題庸汗,而且這種難以跟蹤難以重現(xiàn)的錯誤很可能會隱藏很久。

在 JDK 1.5 之后逻锐,SUN 官方調(diào)整了 JVM夫晌,具體化了 volatile 關(guān)鍵字,因此只需要將 sInstance 的定義改成private volatile static Singleton sInstance = null;就可以保證 sInstance 對象每次都是從主內(nèi)存中讀取昧诱,volatile 或多或少會影響到性能,但是考慮程序的正確性所袁,這點(diǎn)犧牲還是值得的盏档。

DCL 的優(yōu)點(diǎn):資源利用率高;缺點(diǎn):第一次加載時反應(yīng)稍慢燥爷,在高并發(fā)環(huán)境下也有很小概率的缺陷蜈亩。DCL 單例模式是使用最多的單例實(shí)現(xiàn)方式,基本能滿足需求前翎。

(3)靜態(tài)內(nèi)部類單例模式

DCL 雖然在一定程度上解決了資源消耗稚配、多余的同步、線程安全等問題港华,但是它還是存在某些情況下出現(xiàn)失效的問題道川,這個問題被稱為雙重檢查鎖定(DCL)失效,建議使用如下的代碼代替:

public class Singleton {

    private Singleton() {}
    
    public static Singleton getInstance() {

        return SingletonHolder.sInstance;
    }

    /**
     * 靜態(tài)內(nèi)部類
     */
    private static class SingletonHolder {
        private static final Singleton sInstance = new Singleton();
    }
}

當(dāng)?shù)谝淮渭虞d Singleton 類時并不會初始化 sInstance立宜,只有在第一次調(diào)用 getInstance 方法時才會初始化 sInstance冒萄。第一次調(diào)用 getInstance 方法會導(dǎo)致虛擬機(jī)加載 SingletonHolder 類,這種方式不僅能夠確保線程安全橙数,也能保證單例對象的唯一性尊流,同時也延遲了單例的實(shí)例化,所以這是推薦使用的單例模式實(shí)現(xiàn)方式灯帮。

(4)枚舉單例

public enum  Singleton {

    INSTANCE;

    public void doSomething() {
        System.out.println("");
    }
}

寫法簡單是枚舉單例最大的有點(diǎn)崖技,枚舉在 Java 中與普通的類是一樣的。不僅能夠擁有自己的字段钟哥,還能夠有自己的方法迎献,最終的是默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,并且在任何情況下它都是一個單例瞪醋。

在上面(1)忿晕、(2)、(3)的三種單例模式實(shí)現(xiàn)中银受,當(dāng)一個單例的實(shí)例對象通過序列化被寫到磁盤践盼,然后再讀出來鸦采,從而獲得一個實(shí)例,即使構(gòu)造方法是私有的咕幻,反序列化依然可以通過特殊的途徑去創(chuàng)建類的一個新的實(shí)例渔伯,相當(dāng)于調(diào)用了該類的構(gòu)造函數(shù)(例如:繼承 Serializable,寫到磁盤是序列化操作肄程,讀取是反序列化操作)锣吼。反序列化操作提供了一個很特別的鉤子函數(shù),類中具體有一個私有的蓝厌、被實(shí)例化的方法 readReslove 方法玄叠,這個方法直接加入到單例模式中就可以讓開發(fā)人員控制對象的反序列話操作。(這應(yīng)該是底層的知識了拓提,懵逼~~~)

    private Object readReslove()throws ObjectStreamException {
        return sInstance;
    }

也就是在 readReslove 方法中將 sInstance 對象返回读恃,而不是默認(rèn)生成一個新的對象,JDK 5 的 enum 類型系統(tǒng)已經(jīng)處理了這個readresolve的情況代态。

(5)使用容器實(shí)現(xiàn)單例模式

public class  SingletonMnager {


    private static Map<String, Object> objMap = new HashMap<>();
    
    private SingletonMnager(){}


    public static void registerService(String key,Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }

    public static Object getService(String key) {
        return objMap.get(key);
    }
}

在程序的初識寺惫,將多種單例類型注入到一個統(tǒng)一的管理類中,在使用時根據(jù) key 獲取對象對應(yīng)類型的對象蹦疑。這種方式使得我們可以管理多種類型的單例西雀,并且在使用時可以通過統(tǒng)一的接口進(jìn)行獲取操作,降低了用戶的使用成本歉摧,也對用戶因此了具體實(shí)現(xiàn)艇肴,降低了耦合度。

不管一那種形式實(shí)現(xiàn)單例模式判莉,它們的核心原理都是將構(gòu)造函數(shù)私有化豆挽,并且通過靜態(tài)方法獲取一個唯一的實(shí)例,選擇哪種實(shí)現(xiàn)方式取決于項(xiàng)目本身券盅,如是否復(fù)雜的并發(fā)環(huán)境帮哈、JDK 版本是否過低,單例對象的資源消耗等锰镀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末娘侍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子泳炉,更是在濱河造成了極大的恐慌憾筏,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件花鹅,死亡現(xiàn)場離奇詭異氧腰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門古拴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箩帚,“玉大人,你說我怎么就攤上這事黄痪〗襞粒” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵桅打,是天一觀的道長是嗜。 經(jīng)常有香客問我,道長挺尾,這世上最難降的妖魔是什么鹅搪? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮遭铺,結(jié)果婚禮上涩嚣,老公的妹妹穿的比我還像新娘。我一直安慰自己掂僵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布顷歌。 她就那樣靜靜地躺著锰蓬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪眯漩。 梳的紋絲不亂的頭發(fā)上芹扭,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機(jī)與錄音赦抖,去河邊找鬼舱卡。 笑死,一個胖子當(dāng)著我的面吹牛队萤,可吹牛的內(nèi)容都是我干的轮锥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼要尔,長吁一口氣:“原來是場噩夢啊……” “哼舍杜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赵辕,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤既绩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后还惠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饲握,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了救欧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衰粹。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖颜矿,靈堂內(nèi)的尸體忽然破棺而出寄猩,到底是詐尸還是另有隱情,我是刑警寧澤骑疆,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布田篇,位于F島的核電站,受9級特大地震影響箍铭,放射性物質(zhì)發(fā)生泄漏泊柬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一诈火、第九天 我趴在偏房一處隱蔽的房頂上張望兽赁。 院中可真熱鬧,春花似錦冷守、人聲如沸刀崖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽亮钦。三九已至,卻和暖如春充活,著一層夾襖步出監(jiān)牢的瞬間蜂莉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工混卵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留映穗,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓幕随,卻偏偏與公主長得像蚁滋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子合陵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

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