Android設計模式解析(二)單例模式(Singleton Pattern)

前言

單例顯而易見也就是單獨、唯一的意思,即一個類只有一個實例,比較簡單、常用的一種設計模式

舉個栗子

皇帝:

中國有許多的皇帝,一般同一時間段只會有一個皇帝(個別的兩個皇帝的就不說了啊),那我們就能認爲皇帝就是一個單例模式,在這個場景中,有皇帝、大臣,大臣每天都需要去上朝參見皇帝,今天參見的皇帝和昨天前天參見的皇帝都是一樣的。單例模式,絕對的單例模式

使用場景

  • 該類佔用較多的資源,如線程、IO、網絡請求等
  • 該類的數據是全局的、共享的
  • 該類的實例生命週期應該是全局的,在application整個生命週期中都會用到的

實現單例模式的幾種方法

實現單例模式需要關注幾個關鍵點

  • 構造函數私有化,即權限設置爲private
  • 通過一個構造方法或者枚舉返回單例類對象
  • 確保單例類的對象只有一個,尤其是多線程的情況下
  • 確保單例對象在反序列表不會重複創建對象

構造函數的私有化,確保了使用者不能夠通過new來創建單例類的實例對象,該類暴露給外界一個獲取唯一實例的方法,同樣還需要保證線程安全,不能夠在多線程的時候出現單例類存在多個對象的情況,即單例類在生命週期中只能存在一個實例供外界使用。

實現單例的方式

餓漢式

  private static Singleton singleton= new Singleton();
    private Singleton(){
    }
    private static Singleton getInstance(){
        return singleton
    }

餓漢式由於類加載時就創建好了對象,不存在線程安全和效率的問題,缺點是過早的創建了對象,而且不能夠傳遞參數。
肯定也沒人這麼寫,假如有推出去斬了

懶漢式

//定義一個實例
    private static Singleton singleton = null;

    /**
     * 構造函數私有化
     */
    private Singleton() {
    }

    /**
     * 提供一個靜態函數,獲取實例,synchronized進行同步
     * @return
     */
    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

這種方式簡單,但是每次創建實例對象的時候都需要同步,同步是需要消耗資源的,效率低,不建議使用

Double Check Lock實現單例——改進的懶漢式

目前使用最廣泛的單例,線程安全並且效率不低,可以考慮使用

 //定義一個實例
    private volatile static Singleton singleton = null;

    /**
     * 構造函數私有化
     */
    private Singleton() {
    }

    /**
     * 提供一個靜態函數,獲取實例,synchronized進行同步
     *
     * @return
     */
    public static Singleton getInstance() {
        if (singleton == null) {//第一次檢查
            synchronized (Singleton.class) {//lock
                if (singleton == null) {//第二次檢查
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

注意到使用了volatile關鍵字修飾單例對象,這樣可以保證singleton對象每次都從主內存中讀取,從而避免了由與java內存模型帶來不必要的麻煩。
synchronize沒有加在方法上,而是加在了方法體上,調用該方法的時候先檢查實例對象爲空,爲空的時候加同步(防止多線程出現多個實例的情況),並且再次判斷實例是否爲空,爲空就創建。這種方式只會在第一次實例爲空的情況下同步,克服了懶漢式模式下每次都需要同步的情況,避免了不必要的開支

靜態內部類實現單例

比較好的單例,線程安全,保證唯一,延遲實例化。建議選用

 /**
     * 構造函數私有化
     */
    private Singleton() {
    }

    /**
     * 私有內部靜態類,利用了加載外部類的時候內部類不會立即被加載的特性
     */
    private static class SingletonHolder{
        private static Singleton singleton = new Singleton();
    }

    /**
     * 提供一個靜態函數,獲取實例
     *
     * @return
     */
    public static Singleton getInstance() {

        return SingletonHolder.singleton;
    }

使用了java內部類的加載機制,只有內部類的靜態成員被調用的時候纔會加載靜態內部類,所以會延遲加載

枚舉單例

public enum  Singleton {
    SINGLETON;


  private Singleton(){

    }
}

枚舉和java中其他類一樣,不僅可以有字段,也可以有自己的方法,如果我們需要一個對象來實現一個簡單的事件(顯示Toast),就可以使用枚舉,枚舉默認就是單一實例的並且創建默認是線程安全的

使用容器使用單例

 private static Map<String, Object> instanceMap = new HashMap<String, Object>();

    private Singleton() {

    }

    public static void putInstance(String key, Object instance) {
        if (!instanceMap.containsKey(key)) {
            instanceMap.put(key, instance);
        }
    }
    public static Object getInstance(String key){
        return instanceMap.get(key);
    }

這種單例模式在Android源碼中有用到,在單例比較多的時候,通過容器來管理和獲取單例實例是一個不錯的辦法,通常在程序初始化的時候會將多個單例實例存到容器中,這樣可以管理這些實例,下次使用直接取。

總結

單例模式是面向對象設計模式中應用最簡單、最廣泛的一種設計模式

  • 單例模式的優點
    1. 提高了系統的性能
    2. 減少了系統內存的開銷
    3. 單例模式避免對資源的多重佔用保證系統整體的協調
    4. 單例對象可以設置全局的通訊站點, 優化和共享資源訪問
  • 單例迷失的缺點
    1. 單例模式一般不存在接口,也不是抽象的,不易拓展,如要拓展需要改變類的源代碼,這和開閉原則相違背
    2. 單例模式的對象很容易產生一些問題,如:如爲了節省資源將數據庫連接池對象設計爲的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認爲是垃圾而被回收,這將導致對象狀態的丟失。
    3. 單例類的職責過重,某種程度違背了單一職責原則
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章