大話西遊之設計模式_從猴王出世看singleton

myhc發表於2015-10-16

猴王出世

盤古開天闢地之後,天下分為四大部洲,在東勝神州傲來國的海邊上有一個花果山,此山風景秀麗,美不勝收,又多桃樹,遂成了獼猴的天堂。
花果山上有一塊仙石,此石自天地開始便吸收天地靈氣,慢慢周身生成360竅,開始呼吸,好像嬰兒一樣。終於有一天能量充滿,從石頭裡蹦出來個靈胎,見風化為一個石猴,出來之後,眼裡冒出兩道神光,先拜天地,再拜四方,驚動的天上的玉皇大帝,之後石猴喝了人間的河水,把靈氣壓住,成了一個普通的石猴。石猴性格灑脫、好動、很快和獼猴們打成一片,吃飯、玩耍、睡覺都在一起,過著快快樂樂的生活。

故事梗概

有一座山,山上面有一塊仙石,仙頭裡面有個靈胎,靈胎出世,變成石猴;
山上有很多桃樹和獼猴群。
石猴加入到了獼猴群。

程式碼模擬

由於整個模擬過程,程式碼較多,請點選 http://download.csdn.net/detail/myhc2014/9194253 下載,程式碼中更加清晰的瞭解猴王出世故事的流程及程式碼模擬,也能清楚的瞭解到singleton的全部內容,裡面還有其他模式哦。

關鍵程式碼

愛看西遊的人都知道,整個西遊中,一共有多少個孫悟空啊?
你的回答可能是這樣的:
只有一個,天上地下只有一個……
實際上,大家想表達的是只有一個孫悟空。
大家都知道,在物件導向的語言中,一切都是物件,想生成一個物件,只要通過構造方法new一下即可,
那麼,在程式碼中,如何保證只有一個孫悟空?
我是這麼做到的:

public class StoneMonkey extends Lingtai implements IMonkey {

    /**
     * 花果山仙石內的靈胎所變的石猴,天地間只有一個
     */
    private static StoneMonkey stoneMonkey;

    private StoneMonkey(List<LingQi> lingQis) {
        super();
        System.out.println("石猴孕育完成");
    }

    /**
     * 單例
     * 
     * @return
     */
    public static synchronized StoneMonkey getStoneMonkey(List<LingQi> lingQis) {
        if (stoneMonkey == null) {
            stoneMonkey = new StoneMonkey(lingQis);
        }
        return stoneMonkey;
    }

您可能感覺沒啥啊,這個看著沒什麼的程式碼,就能實現?
這個真的能實現,具體原因聽我道來(採用蘇格拉底誘導問答)。

構造方法private,外面能new物件嗎? 不能
外面不能new物件,那麼類內部能訪問private構造方法嗎? 能,但是即使建立了物件,外面也無法訪問啊
如果通過物件呼叫的方式確實無法訪問,但是別忘了,還有可以通過類來呼叫的方法哦 哦,是的,靜態方法的確可以通過類直接進行訪問
那麼通過靜態方法,呼叫能否解決私有構造方法類物件的建立問題? 恩,可以解決的
這樣就完了嗎? 應該可以了
是的,在單執行緒的應用中是可以了,但在多執行緒併發下呢?
synchronized/ lock 可以幫到你

好了,通過上面的對話式描述,對上文的程式碼是不是更清晰了?
其實啊,我用了一個非常簡單的模式來實現這個功能——singleton

singleton? 著挺高階的,他到底是個什麼東西?
讓我們來一步一步的揭開他的神祕面紗

singleton

  1. 試用場景
    當某個事情是獨一無二的,整個程式碼執行環境中,不會在出現第二個物件的時候(如天上地下就一個孫悟空),就可以考慮使用singleton。
  2. 程式碼特點
    2.1. 構造方法私有
    只有構造方法是私有的情況下,其他類才不能直接呼叫該類的構造方法來建立物件,此條件是單例的基礎。
    2.2 開放靜態獲取類例項方法
    在沒有該類物件的情況下,為了讓其他類能正常的獲取到該類的物件,所以需要寫一個public static 的方法,用於返回該類的物件/例項。
    2.3 私有靜態物件例項
    為了保證靜態方法能獲取到例項物件,所需需要一個靜態例項;
    為了保證,外界只能通過開放的靜態同步方法獲取該例項,所以必須為private。
  3. 標準類圖
    後續補上
  4. 標準實現程式碼
    singleton共有兩種寫法,餓漢式 和 懶漢式 singleton,懶漢式還可以進化為 Double-checked Locking ,上文中的程式碼僅僅是懶漢式singleton
    4.1 懶漢式:

        最常用的一種實現方案

public class StoneMonkey extends Lingtai implements IMonkey {

    /**
     * 花果山仙石內的靈胎所變的石猴,天地間只有一個
     */
    private static StoneMonkey stoneMonkey;

    private StoneMonkey(List<LingQi> lingQis) {
        super();
        System.out.println("石猴孕育完成");
    }

    /**
     * 單例
     * 
     * @return
     */
    public static synchronized StoneMonkey getStoneMonkey(List<LingQi> lingQis) {
        if (stoneMonkey == null) {
            stoneMonkey = new StoneMonkey(lingQis);
        }
        return stoneMonkey;
    }

       有些朋友可能會問,為啥在getStoneMonkey前加了一個synchronized
       因為在多執行緒的應用裡,不加 synchronized 可能會出現問題。
       示例如下:
       假設對 getStoneMonkey()方法的兩個呼叫幾乎同時發生,這時候會發生什麼?
       1. 第一個執行緒檢測例項是否存在,因為例項不存在,該執行緒執行建立第一個例項的程式碼部分(new StoneMonkey(lingQis))。
       2. 然而,假設在例項化完成之前,另一個執行緒也來檢測例項成員變數是否為null。因為第一個執行緒還什麼都沒有建立,例項成員變數仍然等於null,所以第二個執行緒也執行了建立一個物件的程式碼(new StoneMonkey(lingQis))。
       3. 現在,兩個執行緒都執行了 singleton物件的new操作,因此建立了兩個物件。
       影響可想而知(出現了多個孫悟空)!
       為了解決這個問題,可以對整個獲取例項物件方法加鎖(synchronized)。

懶漢式構造物件特點:
在其他類呼叫“get例項物件方法”(如getStoneMonkey(List lingQis)) 的時候,在建立物件。
優點: 由於在需要的時候,才例項化物件,故在不需要使用物件的時候,無沒有必要的記憶體佔用(相對於餓漢式)。
缺點: 由於在需要的時候,才例項化物件,故獲“get例項物件方法”耗時長

4.2 Double-checked Locking

        Double-checked Locking 只適用於多執行緒程式,如果在單執行緒的情況下,Double-checked Locking 是沒有意義的(根本就不需要考慮多執行緒問題)。
        鑑於懶漢式的建立方法中,synchronized 鎖定了整個方法,而其他執行緒需要獲取該物件時,必須等待。這將導致一個瓶頸。
        為了解決這個瓶頸問題,採用雙重校驗的方式進行相應的解決。

public class StoneMonkey2 extends Lingtai implements IMonkey {

    /**
     * 花果山仙石內的靈胎所變的石猴,天地間只有一個
     */
    private static StoneMonkey2 stoneMonkey;

    private StoneMonkey2(List<LingQi> lingQis) {
        super();
        System.out.println("石猴孕育完成");
    }

    /**
     * 雙重檢測加鎖
     * 
     * @return
     */
    public static StoneMonkey2 getStoneMonkey(List<LingQi> lingQis) {
        if (stoneMonkey == null) {
            synchronized (StoneMonkey2.class) {
                if (stoneMonkey == null) {
                    stoneMonkey = new StoneMonkey2(lingQis);
                }
            }
        }
        return stoneMonkey;
    }

       特點: 在建立物件前,新增一次檢查,避免不必要的鎖定

4.3 餓漢式

public class StoneMonkey3 extends Lingtai implements IMonkey {

    /**
     * 花果山仙石內的靈胎所變的石猴,天地間只有一個
     */
    private static StoneMonkey3 stoneMonkey = new StoneMonkey3();;

    private StoneMonkey3() {
        super();
        System.out.println("石猴孕育完成,沒有吸收靈氣");
    }

    /**
     * 餓漢式
     * 
     * @return
     */
    public static StoneMonkey3 getStoneMonkey(List<LingQi> lingQis) {
        return stoneMonkey;
    }

       特點:在類被虛擬機器載入時就自動呼叫構造方法(StoneMonkey3())生成例項物件了。
       優點: 由於類被載入的時候,就建立了例項,故外面獲取例項物件時,耗時短。
       缺點:由於類被載入的時候,就建立了例項,故可能會造成無用的記憶體佔用。

總結:

       單例模式是非常非常簡單的一個模式,其兩種實現方式也是比較簡單的,至於平時的使用,建議大家多考慮一下場景,也多想一下孫悟空。

本人技術有限,如果有說不對的地方,歡迎隨時指正。
非常歡迎大家留言交流,讓我們共同進步~

相關文章