劍指Offer面試題02:單例模式

Intent

確保一個類只有一個實例祭芦,并提供該實例的全局訪問點。

Class Diagram

使用一個私有構(gòu)造函數(shù)憔鬼、一個私有靜態(tài)變量以及一個公有靜態(tài)函數(shù)來實現(xiàn)龟劲。

私有構(gòu)造函數(shù)保證了不能通過構(gòu)造函數(shù)來創(chuàng)建對象實例,只能通過公有靜態(tài)函數(shù)返回唯一的私有靜態(tài)變量轴或。

Implementation

Ⅰ 懶漢式-線程不安全

以下實現(xiàn)中昌跌,私有靜態(tài)變量 uniqueInstance 被延遲實例化,這樣做的好處是照雁,如果沒有用到該類蚕愤,那么就不會實例化 uniqueInstance,從而節(jié)約資源饺蚊。

這個實現(xiàn)在多線程環(huán)境下是不安全的萍诱,如果多個線程能夠同時進入 if (uniqueInstance == null) ,并且此時 uniqueInstance 為 null污呼,那么會有多個線程執(zhí)行 uniqueInstance = new Singleton(); 語句裕坊,這將導(dǎo)致實例化多次 uniqueInstance。

public class Singleton {

    private static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

Ⅱ 餓漢式-線程安全

線程不安全問題主要是由于 uniqueInstance 被實例化多次燕酷,采取直接實例化 uniqueInstance 的方式就不會產(chǎn)生線程不安全問題籍凝。

但是直接實例化的方式也丟失了延遲實例化帶來的節(jié)約資源的好處。

private static Singleton uniqueInstance = new Singleton();

Ⅲ 懶漢式-線程安全

只需要對 getUniqueInstance() 方法加鎖苗缩,那么在一個時間點只能有一個線程能夠進入該方法饵蒂,從而避免了實例化多次 uniqueInstance。

但是當(dāng)一個線程進入該方法之后酱讶,其它試圖進入該方法的線程都必須等待退盯,即使 uniqueInstance 已經(jīng)被實例化了。這會讓線程阻塞時間過程,因此該方法有性能問題得问,不推薦使用囤攀。

public static synchronized Singleton getUniqueInstance() {
    if (uniqueInstance == null) {
        uniqueInstance = new Singleton();
    }
    return uniqueInstance;
}

Ⅳ 雙重校驗鎖-線程安全

uniqueInstance 只需要被實例化一次软免,之后就可以直接使用了宫纬。加鎖操作只需要對實例化那部分的代碼進行,只有當(dāng) uniqueInstance 沒有被實例化時膏萧,才需要進行加鎖漓骚。

雙重校驗鎖先判斷 uniqueInstance 是否已經(jīng)被實例化,如果沒有被實例化榛泛,那么才對實例化語句進行加鎖蝌蹂。

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

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

考慮下面的實現(xiàn),也就是只使用了一個 if 語句曹锨。在 uniqueInstance == null 的情況下孤个,如果兩個線程都執(zhí)行了 if 語句,那么兩個線程都會進入 if 語句塊內(nèi)沛简。雖然在 if 語句塊內(nèi)有加鎖操作齐鲤,但是兩個線程都會執(zhí)行 uniqueInstance = new Singleton(); 這條語句,只是先后的問題椒楣,那么就會進行兩次實例化给郊。因此必須使用雙重校驗鎖,也就是需要使用兩個 if 語句捧灰。

if (uniqueInstance == null) {
    synchronized (Singleton.class) {
        uniqueInstance = new Singleton();
    }
}

uniqueInstance 采用 volatile 關(guān)鍵字修飾也是很有必要的淆九。uniqueInstance = new Singleton(); 這段代碼其實是分為三步執(zhí)行。

  1. 為 uniqueInstance 分配內(nèi)存空間
  2. 初始化 uniqueInstance
  3. 將 uniqueInstance 指向分配的內(nèi)存地址

但是由于 JVM 具有指令重排的特性毛俏,執(zhí)行順序有可能變成 1>3>2炭庙。指令重排在單線程環(huán)境下不會出先問題,但是在多線程環(huán)境下會導(dǎo)致一個線程獲得還沒有初始化的實例煌寇。例如焕蹄,線程 T1 執(zhí)行了 1 和 3,此時 T2 調(diào)用 getUniqueInstance() 后發(fā)現(xiàn) uniqueInstance 不為空唧席,因此返回 uniqueInstance擦盾,但此時 uniqueInstance 還未被初始化。

使用 volatile 可以禁止 JVM 的指令重排淌哟,保證在多線程環(huán)境下也能正常運行迹卢。

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

當(dāng) Singleton 類加載時,靜態(tài)內(nèi)部類 SingletonHolder 沒有被加載進內(nèi)存徒仓。只有當(dāng)調(diào)用 getUniqueInstance() 方法從而觸發(fā) SingletonHolder.INSTANCE 時 SingletonHolder 才會被加載腐碱,此時初始化 INSTANCE 實例,并且 JVM 能確保 INSTANCE 只被實例化一次。

這種方式不僅具有延遲初始化的好處症见,而且由 JVM 提供了對線程安全的支持喂走。

public class Singleton {

    private Singleton() {
    }

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

    public static Singleton getUniqueInstance() {
        return SingletonHolder.INSTANCE;
    }
}

該實現(xiàn)在多次序列化再進行反序列化之后,不會得到多個實例谋作。而其它實現(xiàn)芋肠,為了保證不會出現(xiàn)反序列化之后出現(xiàn)多個實例,需要使用 transient 修飾所有字段遵蚜,并且實現(xiàn)序列化和反序列化的方法帖池。

該實現(xiàn)可以防止反射攻擊。在其它實現(xiàn)中吭净,通過 setAccessible() 方法可以將私有構(gòu)造函數(shù)的訪問級別設(shè)置為 public睡汹,然后調(diào)用構(gòu)造函數(shù)從而實例化對象,如果要防止這種攻擊寂殉,需要在構(gòu)造函數(shù)中添加防止實例化第二個對象的代碼囚巴。但是該實現(xiàn)是由 JVM 保證只會實例化一次,因此不會出現(xiàn)上述的反射攻擊友扰。

Examples

  • Logger Classes
  • Configuration Classes
  • Accesing resources in shared mode
  • Factories implemented as Singletons

JDK

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末彤叉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子焕檬,更是在濱河造成了極大的恐慌姆坚,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件实愚,死亡現(xiàn)場離奇詭異兼呵,居然都是意外死亡,警方通過查閱死者的電腦和手機腊敲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門击喂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人碰辅,你說我怎么就攤上這事懂昂。” “怎么了没宾?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵凌彬,是天一觀的道長。 經(jīng)常有香客問我循衰,道長铲敛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任会钝,我火速辦了婚禮伐蒋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己先鱼,他們只是感情好俭正,可當(dāng)我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著焙畔,像睡著了一般掸读。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闹蒜,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天寺枉,我揣著相機與錄音抑淫,去河邊找鬼绷落。 笑死,一個胖子當(dāng)著我的面吹牛始苇,可吹牛的內(nèi)容都是我干的砌烁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼催式,長吁一口氣:“原來是場噩夢啊……” “哼函喉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起荣月,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤管呵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哺窄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捐下,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年萌业,在試婚紗的時候發(fā)現(xiàn)自己被綠了坷襟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡生年,死狀恐怖婴程,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抱婉,我是刑警寧澤档叔,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蒸绩,受9級特大地震影響衙四,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜侵贵,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一届搁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦卡睦、人聲如沸宴胧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恕齐。三九已至,卻和暖如春瞬逊,著一層夾襖步出監(jiān)牢的瞬間显歧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工确镊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留士骤,地道東北人。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓蕾域,卻偏偏與公主長得像拷肌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子旨巷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,960評論 2 355