Java Singleton 單例模式

單例模式

  • 屬于創(chuàng)建型模式

  • 自行完成實(shí)例化搁骑,私有化構(gòu)造函數(shù)

單例模式的目標(biāo)

  • 實(shí)例唯一性
  • 線程安全性

任何情況都需要確保一個(gè)類只存在一個(gè)實(shí)例篮奄,不會因?yàn)槎嗑€程的訪問而導(dǎo)致創(chuàng)建多個(gè)實(shí)例允懂,同時(shí)也不會因?yàn)槎嗑€程而引入新的效率問題

1 餓漢式

//原理:通過 JVM 在加載類的時(shí)候來完成靜態(tài)對象的初始化钙畔,而這個(gè)過程本身就是線程安全的(類初始化鎖保證線程安全)千元,無法實(shí)現(xiàn)懶加載哈误,完全依賴虛擬機(jī)加載類的策略進(jìn)行加載
//1.1 靜態(tài)常量
public class Singleton {
    //基于 Class Loader 類加載機(jī)制
    private final static Singleton INSTANCE = new Singleton();
    // 私有構(gòu)造方法暖眼,防止被外部直接實(shí)例化 
    private Singleton(){}
    public static Singleton getInstance(){
        return INSTANCE;
    }
}
//1.2 靜態(tài)全局變量
public class Singleton {
    private static Singleton sInstance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return sInstance;
    }
}
//1.3 靜態(tài)全局變量 靜態(tài)代碼塊
public class Singleton {
    private static Singleton sInstance;
    static {
        sInstance = new Singleton();
    }
    private Singleton() {}
    public Singleton getInstance() {
        return sInstance;
    }
}
  • 通過變通避免了多線程的同步問題
  • 延長了類加載的時(shí)間惕耕,效率比較低
  • 已經(jīng)加載,如果最終未使用诫肠,就造成了浪費(fèi)資源

2 懶漢式

//2.1 線程不安全
public class Singleton {
    private static Singleton sInstance;
    private Singleton() {}
    /**
    *只適合在單線程情況下使用司澎,如果是多線程情況下,一個(gè)線程進(jìn)入 if (singleton == null) 判斷語句塊栋豫,還沒來得及往下執(zhí)行挤安,另一個(gè)線程也通過了這個(gè) if 判斷語句,此時(shí)就會產(chǎn)生多個(gè)實(shí)例
    */
    public static Singleton getInstance() {
        if (sInstance == null) {
            sInstance = new Singleton();
        }
        return sInstance;
    }
}
//2.2 上面的例子 getInstance() 方法加 synchronized 關(guān)鍵字丧鸯,線程安全
public class Singleton {
    private static Singleton sInstance;
    private Singleton() {}
    //對 getInstance() 進(jìn)行了線程同步蛤铜,每次 getInstance 要進(jìn)行同步,大大增加了開銷
    public synchronized static Singleton getInstance() {
        if (sInstance == null) {
            sInstance = new Singleton();
        }
        return sInstance;
    }
}
//2.3 實(shí)例化外面加了 synchronized(Singleton.class) { } 代碼塊丛肢,線程不安全
public class Singleton {
    private static Singleton sInstance;
    private Singleton() {}
    public static Singleton getInstance() {
    if (sInstance == null) {
        //多個(gè)線程仍舊能夠通過 if 判斷围肥,雖然同步了實(shí)例化的代碼,但還是會多次實(shí)例化
        synchronized (Singleton.class) {
            sInstance = new Singleton();
        }
    }
    return sInstance;
    }
}
//2.4 上面的例子 if 判斷外面加 synchronized(Singleton.class) {} 代碼塊蜂怎,其實(shí)和 2.2 一樣穆刻,線程安全
public class Singleton {
    private static Singleton sInstance;
    private Singleton() { }
    public static Singleton getInstance() {
        synchronized (Singleton.class) {
            if (sInstance == null) {
                sInstance = new Singleton();
            }
        }
        return sInstance;
    }
}

3 雙重檢查鎖

// Double-Checked Locking 雙重檢查鎖 DCL,進(jìn)行了兩次判空
public class Singleton {
    //volatile 關(guān)鍵字聲明杠步,會在編譯時(shí)加 lock氢伟,禁止了指令重排序
    private static volatile Singleton sInstance;
    private Singleton() {}
    public static Singleton getInstance() {
        //判斷一次避免不必要的同步鎖
        if (sInstance == null) {
            synchronized (Singleton.class) {
                if (sInstance == null) {
                    sInstance = new Singleton();
                }
            }
        }
        return sInstance;
    }
}
  • 線程安全,延遲加載幽歼,效率較高

  • 需要使用 volatile 的原因

    系統(tǒng)執(zhí)行 sInstance = new Singleton() 這段代碼不是一次性完成的朵锣,大概分為三步指令

    1 為需要創(chuàng)建的 Singleton 實(shí)例分配內(nèi)存
    2 調(diào)用對應(yīng) Singleton 的構(gòu)造方法
    3 將 sInstance 指向前面分配的內(nèi)存空間,此時(shí) sInstance 不為 null 了

    而指令重排序可能會出現(xiàn)先執(zhí)行 3 再執(zhí)行 2 的情況试躏,所以當(dāng)一個(gè)線程執(zhí)行了 1 和 3 但還沒執(zhí)行 2 (后面走完就會創(chuàng)建一個(gè)實(shí)例)猪勇,而此時(shí)另一個(gè)線程就能通過空判斷而創(chuàng)建了另一個(gè)實(shí)例

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

//
public class Singleton {
    private Singleton() {}
    //靜態(tài)內(nèi)部類
    private static class SingletonInstanceHolder {
        //內(nèi)部類的加載機(jī)制,類的靜態(tài)屬性只會在第一次加載該類的時(shí)候進(jìn)行初始化
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonInstanceHolder.INSTANCE;
    }
}
  • 避免了線程不安全颠蕴,延遲加載泣刹,效率高

5 枚舉式

public enum Singleton {
    INSTANCE;
    public void doSome1(){
    }
}
  • 避免了線程不安全
  • 防止反序列化重新創(chuàng)建新的對象

注意

1 以上方案不考慮反序列化的情況

  • 反序列化時(shí)會調(diào)用 readResolve() 方法重新生成一個(gè)實(shí)例助析,所以需要覆寫直接返回單例對象

2 通過用 Map 存值的方式也可以實(shí)現(xiàn)單例的概念

3 Android 有自帶的 Singleton 抽象類


/**
 * Singleton helper class for lazily initialization.
 *
 * Modeled after frameworks/base/include/utils/Singleton.h
 *
 * @hide
 */
//源碼位置 /frameworks/base/core/java/android/util/Singleton.java
public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

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

思考:在 Java 中構(gòu)造一個(gè)普通對象的成本比較低,那為什么針對單例模式要如此糾結(jié)是否是懶加載呢椅您?

  • 加載時(shí):通常單例的生命周期較長外冀,假設(shè)單例類本身在初始化的時(shí)候涉及大量業(yè)務(wù)邏輯,比較復(fù)雜或耗時(shí)掀泳,那是不是不太適合懶加載雪隧,反而需要提前加載呢 ?
  • 加載后:假設(shè)單例類初始化后持有了大量資源员舵,如果初始化后卻沒能使用上了就顯得很浪費(fèi)脑沿,那不管用沒用上內(nèi)存的風(fēng)險(xiǎn)一直都存在著,是否就意味著本身就需要進(jìn)行內(nèi)存優(yōu)化了呢马僻?

所以如果把單例類設(shè)計(jì)得比較輕庄拇,是否就不用過多去糾結(jié)懶加載的問題呢?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末韭邓,一起剝皮案震驚了整個(gè)濱河市措近,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌女淑,老刑警劉巖瞭郑,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鸭你,居然都是意外死亡屈张,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門苇本,熙熙樓的掌柜王于貴愁眉苦臉地迎上來袜茧,“玉大人,你說我怎么就攤上這事瓣窄。” “怎么了纳鼎?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵俺夕,是天一觀的道長。 經(jīng)常有香客問我贱鄙,道長劝贸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任逗宁,我火速辦了婚禮映九,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瞎颗。我一直安慰自己件甥,他們只是感情好捌议,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著引有,像睡著了一般瓣颅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上譬正,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天宫补,我揣著相機(jī)與錄音,去河邊找鬼曾我。 笑死粉怕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抒巢。 我是一名探鬼主播贫贝,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼虐秦!你這毒婦竟也來了平酿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤悦陋,失蹤者是張志新(化名)和其女友劉穎蜈彼,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俺驶,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡幸逆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了暮现。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片还绘。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖栖袋,靈堂內(nèi)的尸體忽然破棺而出拍顷,到底是詐尸還是另有隱情,我是刑警寧澤塘幅,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布昔案,位于F島的核電站,受9級特大地震影響电媳,放射性物質(zhì)發(fā)生泄漏踏揣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一匾乓、第九天 我趴在偏房一處隱蔽的房頂上張望捞稿。 院中可真熱鬧,春花似錦、人聲如沸娱局。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽铃辖。三九已至剩愧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間娇斩,已是汗流浹背仁卷。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留犬第,地道東北人锦积。 一個(gè)月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像歉嗓,于是被迫代替她去往敵國和親丰介。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

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