設計模式之單例模式

設計模式之單例模式


單例模式

“單例(Singleton)”簡單理解就是單一實例,全局最多隻能創建一個該類的實例對象,該類提供得到該實例的全局訪問點,通常是getInstance()這個靜態方法。可能你會疑問了,那我想得到對象,我new不就行了?對不起,這個類的構造方法修飾符是private,沒給你new的機會啊。那好吧。。。我們來看一下最簡單的例子:

public class Singleton {

    private static Singleton mInstance; // Singleton模式全局唯一的實例對象
    private static int count = 0;       // 代表當前類被實例化的次數

    //私有構造方法,讓你new無可new
    private Singleton() {
        count++;
        System.out.println("啊,被實例化了" + count);
    }

    /**
     * 全局提供的靜態方法訪問點
     * @return 全局唯一的Singleton實例對象
     */
    public static Singleton getInstance() {
        if (mInstance == null) {
            mInstance = new Singleton();
        }
        return mInstance;
    }

    /**
     * 打印一下當前對象的hashcode
     */
    public void printHashCode() {
        System.out.println("hashCode--->" + this.hashCode());
    }
}

接下來,看看測試類:

public class Test {

    public static void main(String[] args) {
//      Singleton singleton = new Singleton();  //這個肯定報錯的
        Singleton st1 = Singleton.getInstance(); //這個嘛就是我們想要的了
        st1.printHashCode();
        Singleton st2 = Singleton.getInstance(); //再來一個
        st2.printHashCode(); 
    }
}

你猜結果:

啊,被實例化了1
hashCode--->366712642
hashCode--->366712642

沒有出乎你的想象吧,這兩個實例的hashcode是相同的,而且構造方法只被調用了一次。注意點:
1. getInstance()這個方法必須是靜態的;
2. 內部的對象mInstance也必須是靜態的,不然靜態方法怎麼訪問非靜態成員呢?
3. 構造方法必須是private修飾的,不然可以被各種new,全局可就有數不清的Singleton對象了(好奇怪)。


你以爲這樣就完了?好吧,單線程中,這樣確實差不多了哈,但是一遇到多線程,你可就要仔細仔細考慮了,來看個示意圖吧

public static Singleton getInstance() {
        if (mInstance == null) { //①
            mInstance = new Singleton();//②
        }
        return mInstance;
    }

當兩個線程同時執行到①這兒,是不是就有機會創造不同的對象了呢?答案是肯定的啊!
我們把測試類中主方法內容改一改:

public static void main(String[] args) {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            Singleton st = Singleton.getInstance();
            st.printHashCode();
        }
    };
    new Thread(r).start();//來一個新線程
    new Thread(r).start();//再來一個新線程
    //你還可以多來幾個新線程,看看結果亂成什麼樣子,一定不會讓你失望的
}

看到結果,我是平靜的,完全意料之中

啊,被實例化了2
啊,被實例化了2
hashCode--->358411109
hashCode--->1980094282

那該怎麼解決呢?想必你已經想到了,線程同步加synchronized關鍵字,那我們不妨試試,public synchronized static Singleton getInstance()僅僅多加了這個關鍵字,其他啥都變,再試試,結果一定是您期望的那樣,這兒就不貼出來了,就讓客官自己動手寫寫看效果吧。


擴展
談到這兒,基本概念也應該知道了吧,那我們再擴展擴展唄,把傳說中的“餓漢模式”、“懶漢模式”說說唄。
這兩種都是單例模式,只是具體實現方式不同。

懶漢模式

其實前面寫的代碼就是懶漢模式,說他懶,是因爲:每次你調用getInstance()方法的時候,它纔會去判斷該唯一實例對象是否存在,不存在則new出來返回給你。

餓漢模式

說它‘餓’是因爲它真的很餓,你餓了嗎?我好像有點餓了。。。好吧,不知道說到哪兒去了。餓漢預先把對象new出來(就像人,餓了,先吃飽,好像比喻不怎麼恰當,沒關係啦)。上代碼吧

public class LazySingleton {
    //類加載的時候,就預先new出來
    private static LazySingleton mInstance = new LazySingleton();
    private static int count = 0;

    private LazySingleton() {
        System.out.println("count--->" + (++count));
    }
    //直接返回對象
    public static LazySingleton getInstance() {
        return mInstance;
    }

    public void printHashCode() {
        System.out.println("hashcode--->" + this.hashCode());
    }
}

這個好處是:即使多線程,也不用考慮同步的問題,因爲類加載的時候就創建了唯一的對象,要得到實例時,直接返回即可。


好吧,看似很簡單的單例模式,其實也並不簡單,對於餓漢模式,還有一些可以優化的地方,採用“雙重檢查加鎖”,在getInstance()中減少使用同步 。 具體請參考下面:

《Head First 設計模式(中文版)》 page182


題外話
這一段時間,一直在忙另外一個項目,所以沒有更新,不過,以後打算還是要堅持規律的更新一下吧。MarkDown編輯器還不是很熟,慢慢用着用着就好了吧,畢竟以前也沒用過,也沒花時間去學,我承認我有點懶了。
這篇沒有講故事,因爲單身和單例就比較配哦,沒有故事可講,嘻嘻。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章