劍指Offer - 單例模式

Future.png

單例模式

We all want to as a 有追求的程序猿,這不將早已塵封的《劍指Offer》給拿出來(lái)重新拜讀一下缀台。該書(shū)中面試題2就是單例模式,可見(jiàn)其重要性(Maybe just for Interview).同時(shí)也為了這次更加系統(tǒng)地閱讀&總結(jié)哮奇。
PS :之前也是隨便翻了幾下就束之高閣啦膛腐。


前言

我們?cè)诿嬖囍薪?jīng)常遇到單例模式(However you as 面試者or面試官),關(guān)于單例模式的優(yōu)秀文章鼎俘,網(wǎng)上也是俯首皆是哲身。本文Just for me to 心得體會(huì)or筆記。如果有幸能幫助到其他人贸伐,那我將會(huì)更加高興...

1-餓漢式單例

這個(gè)寫(xiě)法就類(lèi)似于解決了單例模式中的“溫飽問(wèn)題”

public class Singleton {
  // JVM加載該類(lèi)時(shí)勘天,單例對(duì)象就會(huì)自動(dòng)創(chuàng)建
    private static Singleton instance = new Singleton();

    private Singleton() {
        System.out.println("構(gòu)造函數(shù)Running....");
    }

    public static Singleton getInstance(){
        return instance;
    }

    /**
     * 證明了沒(méi)有對(duì)instance做延時(shí)加載...
     */
    public static void doSomething(){
        System.out.println("Just for fun...");
    }

    public static void main(String[] args) {
        /*
        * 這里沒(méi)有用到該實(shí)例,But 照樣給我創(chuàng)建了其實(shí)例
        */
        Singleton.doSomething();
    }
}

"Talk is cheap,show me the code"

JVM類(lèi)的加載原理
  1. JVM在執(zhí)行類(lèi)的初始化期間捉邢,JVM會(huì)獲得一把鎖脯丝,該鎖可以同步多個(gè)線(xiàn)程對(duì)同一個(gè)類(lèi)的初始化

There is no doubt that 該種方案實(shí)現(xiàn)簡(jiǎn)單,且線(xiàn)程安全伏伐。但是其沒(méi)有對(duì)instance做相應(yīng)的延時(shí)加載宠进,只要初始化該類(lèi)就創(chuàng)建其實(shí)例,這樣就造成了資源浪費(fèi)藐翎。


2-懶漢式單例

“懶漢式”---顧名思義材蹬,就是你要我才給,按需分配

/**
 * “懶漢式”吝镣,用到實(shí)例才加載堤器,否則不加載
 *
 * 缺點(diǎn):線(xiàn)程不安全...
 */
public class Singleton {
    private static Singleton instance = null;

    private Singleton() {
        System.out.println("構(gòu)造函數(shù)Running...");
    }

    public  static Singleton getInstance(){
        /**
         * 避免重復(fù)創(chuàng)建...
         */
        if (instance == null) {
            instance = new Singleton();
        }
       return instance;
    }

    /**
     * HashCode相等說(shuō)明是同一個(gè)實(shí)例
     * @return
     */
    @Override
    public int hashCode() {
        return super.hashCode();
    }

    public static void main(String[] args) {
        /**
         * 模擬多線(xiàn)程環(huán)境,會(huì)發(fā)現(xiàn)不是同一個(gè)實(shí)例...
         */
        for (int i = 0; i < 5; i++) {
            new Thread(() -> System.out.println(getInstance().hashCode())).start();
        }
    }
}

Code 地址:該編輯器在多線(xiàn)程情況下測(cè)試單例不好使末贾,有興趣可以復(fù)制到本地去運(yùn)行測(cè)試

單例模式.png

(PS:原圖片鏈接

線(xiàn)程不安全闸溃,那我們只能去使用同步機(jī)制來(lái)保證線(xiàn)程安全

3-同步鎖的懶漢式

/**
 *
 * 保證線(xiàn)程安全的“餓漢式”單例
 *
 * 即:加入synchronized 同步關(guān)鍵字...
 *
 * 下面的格式將會(huì)造成:每次來(lái)調(diào)用getInstance()都要進(jìn)行線(xiàn)程同步(即調(diào)用synchronized鎖)
 *
 * 而實(shí)際上, 只需要在第一次調(diào)用的時(shí)候才需要進(jìn)行同步,只要單例存在圈暗,就沒(méi)必要進(jìn)行同步啦...
 *
 */
public class Singleton {
    private static Singleton instance = null;

    private Singleton() {
        System.out.println("構(gòu)造函數(shù)running...");
    }


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

    /**
    * 也可以寫(xiě)成這種格式
    * */

//    public static Singleton getInstance(){
//        synchronized (Singleton.class){
//            if (instance != null) {
//                instance = new Singleton();
//            }
//        }
//        return instance;
//    }
    
    @Override
    public int hashCode() {
        return super.hashCode();
    }
    
    public static void main(String[] args) {
        for (int i = 0; i <5 ; i++) {
            new Thread(() -> System.out.println(Singleton.getInstance().hashCode())).start();
        }
    }
}

Source code

代碼中的注釋已經(jīng)很清楚了掂为,這里就閑話(huà)不扯...

4-雙重校驗(yàn)鎖(Double-Check)單例

這個(gè)是重點(diǎn)...


/**
 * "Double-Check"
 * "雙重校驗(yàn)鎖"的單例...
 *
 *
 * 其實(shí)就是在"同步鎖"的基礎(chǔ)上裕膀,外層 + if 判斷...
 *
 * 作用:若單例存在员串,就不需要進(jìn)行同步加鎖操作synchronized。直接返回實(shí)例昼扛。從而提高程序性能...
 *
 *
 * PS: 到這里還沒(méi)有完工寸齐,主要原因在于 instance = new Singleton2();
 * 這并非是個(gè)原子操作,該句事實(shí)上在JVM中大概有三個(gè)過(guò)程:
 * 1. 給instance 分配內(nèi)存;
 * 2. 調(diào)用Singleton2的構(gòu)造函數(shù)來(lái)初始化成員變量抄谐,生成實(shí)例;
 * 3. 將singleton對(duì)象指向分配的內(nèi)存空間(此時(shí)渺鹦,instance 才是非null的)
 *
 *
 * 但是在JVM的即時(shí)編譯器中存在指令重排的優(yōu)化,so 上述的2蛹含,3順序不能保證毅厚。
 * 假如執(zhí)行序列為1-3-2.,當(dāng)3執(zhí)行完畢浦箱,而2未執(zhí)行之前吸耿,被其他線(xiàn)程搶占了,此時(shí)instance已經(jīng)是非null(但是沒(méi)有初始化)
 * 線(xiàn)程直接返回了instance酷窥,然后使用就報(bào)錯(cuò)...
 *
 * 所以需要在instance聲明為volatile 就可以啦...
 *
 *
 * volatile關(guān)鍵字的兩個(gè)功能:
 * 1. 這個(gè)變量不會(huì)在多個(gè)線(xiàn)程中存在副本咽安,直接從內(nèi)存中讀取...
 * 2. 禁止指令重排序優(yōu)化。
 *
 * 但是這個(gè)只在Java 1.5之后有效蓬推,因?yàn)橹暗腏ava內(nèi)存模型有缺陷...
 *
 * 總結(jié):
 * 該單例版本有點(diǎn)復(fù)雜...
 *
 */
public class Singleton {
    /**
     * 注意這里...volatile關(guān)鍵字
     */
    private volatile static Singleton instance = null;

    private Singleton() {
        System.out.println("running...");
    }

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

    @Override
        public int hashCode() {
        return super.hashCode();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> System.out.println(getInstance().hashCode())).start();
        }
    }
}

雙重校驗(yàn)鎖Source Code

雙重校驗(yàn)鎖是滿(mǎn)足要求妆棒,But 有局限性... 別著急,還有更好的

5- 靜態(tài)內(nèi)部類(lèi)實(shí)現(xiàn)單例

package offer;

/**
 * @author king
 * @date 2018/5/6
 * <p>
 * 靜態(tài)內(nèi)部類(lèi)實(shí)現(xiàn)單例
 */
public class Singleton {
    /**
     * 創(chuàng)建靜態(tài)內(nèi)部類(lèi)
     */
    private static class InnerSingleton {
        /**
         * 在靜態(tài)內(nèi)部類(lèi)里創(chuàng)建單例
         */
        private static Singleton instance = new Singleton();
    }

    /**
     * 私有化構(gòu)造函數(shù)
     */
    private Singleton() {
        System.out.println("構(gòu)造函數(shù)Running...");
    }

    public static Singleton getInstance() {
        return InnerSingleton.instance;
    }

    @Override
    public int hashCode() {
        return super.hashCode();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> System.out.println(getInstance().hashCode())).start();
        }
    }
}

靜態(tài)內(nèi)部類(lèi)單例Source Code


關(guān)于單例的小插曲

曾經(jīng)在面試過(guò)程中沸伏,問(wèn)到面試者餓漢式&懶漢式的區(qū)別:

曾有面試者告訴我糕珊,餓漢式每次加載類(lèi)都會(huì) new 一次對(duì)象,將造成資源浪費(fèi)毅糟。我當(dāng)時(shí)沒(méi)反應(yīng)過(guò)來(lái)红选,只注意到它回答的“資源浪費(fèi)”,后來(lái)我才明白過(guò)來(lái)留特,instance是static的纠脾,只會(huì)初始化一次,何來(lái)多次new 之談蜕青?苟蹈??

  1. 有一次讓面試者寫(xiě)個(gè)懶漢式單例(先不考慮多線(xiàn)程情況)右核,代碼大意如下:
public class Singleton {
  public static  Singleton instance = null;
  private Singleton(){
  }
  //  注意...
  public static Singleton getInstance(){
    instance = new Singleton();
    return instance;
  }

}

少個(gè)判空慧脱,已非“單例”啊...

總結(jié)

無(wú)論是劍指offer這本書(shū),還是我們面試中高級(jí)崗位時(shí)贺喝,考察點(diǎn)基本都會(huì)設(shè)在雙重校驗(yàn)鎖上菱鸥,畢竟面試造核彈...

參考文章

單例模式
最全面的單例講解
深入淺出Singleton

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宗兼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子氮采,更是在濱河造成了極大的恐慌殷绍,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹊漠,死亡現(xiàn)場(chǎng)離奇詭異主到,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)躯概,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)登钥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人娶靡,你說(shuō)我怎么就攤上這事牧牢。” “怎么了姿锭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵塔鳍,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我艾凯,道長(zhǎng)献幔,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任趾诗,我火速辦了婚禮蜡感,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恃泪。我一直安慰自己郑兴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布贝乎。 她就那樣靜靜地躺著情连,像睡著了一般。 火紅的嫁衣襯著肌膚如雪览效。 梳的紋絲不亂的頭發(fā)上却舀,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音锤灿,去河邊找鬼挽拔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛但校,可吹牛的內(nèi)容都是我干的螃诅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼术裸!你這毒婦竟也來(lái)了倘是?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤袭艺,失蹤者是張志新(化名)和其女友劉穎搀崭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體匹表,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡门坷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了袍镀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冻晤,死狀恐怖苇羡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鼻弧,我是刑警寧澤设江,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站攘轩,受9級(jí)特大地震影響叉存,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜度帮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一歼捏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧笨篷,春花似錦瞳秽、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至冕臭,卻和暖如春腺晾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辜贵。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工悯蝉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人念颈。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓泉粉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嗡靡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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

  • 單例模式(SingletonPattern)一般被認(rèn)為是最簡(jiǎn)單跺撼、最易理解的設(shè)計(jì)模式,也因?yàn)樗暮?jiǎn)潔易懂讨彼,是項(xiàng)目中最...
    成熱了閱讀 4,253評(píng)論 4 34
  • 概念 確保某一個(gè)類(lèi)只有一個(gè)實(shí)例歉井,而且自行實(shí)例化,并向整個(gè)系統(tǒng)提供一個(gè)訪(fǎng)問(wèn)它的全局訪(fǎng)問(wèn)點(diǎn)哈误,這個(gè)類(lèi)稱(chēng)為單例類(lèi)哩至。 特性 ...
    野狗子嗷嗷嗷閱讀 545評(píng)論 0 2
  • 【學(xué)習(xí)難度:★☆☆☆☆,使用頻率:★★★★☆】直接出處:?jiǎn)卫J绞崂砗蛯W(xué)習(xí):https://github.com/...
    BruceOuyang閱讀 673評(píng)論 1 2
  • 蔡康永在《康永蜜自,給殘酷社會(huì)的善意短信》中說(shuō)過(guò)這么一段話(huà):15歲覺(jué)得游泳難菩貌,放棄游泳,到18歲遇到一個(gè)你喜歡的...
    遇見(jiàn)胡小菌閱讀 487評(píng)論 0 0
  • 放任自己一整天沉浸在書(shū)中重荠,一房之中歷經(jīng)人世年年箭阶、大落大起、大喜大悲戈鲁。 翻完最后一頁(yè)仇参,放在書(shū)架最上面的格子,心滿(mǎn)意足...
    陸由_閱讀 254評(píng)論 0 0