1. 程式人生 > >【設計模式與Android】單例模式——獨一無二的皇帝

【設計模式與Android】單例模式——獨一無二的皇帝

什麼是單例模式

所謂單例模式,就是確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項的設計模式。單例模式是最簡單的設計模式,也是應用最廣的設計模式。一般用於避免產生多個物件消耗過多的資源或者某種型別的物件必須獨一無二的情景。

單例模式的實現方式

(1)餓漢式

單例模式極其簡單,僅有一個單例類。既然常用於確保某種型別的物件必須獨一無二的情景,那麼我們可以用皇帝來舉例。程式碼如下:

public class Emperor {

    //初始化一個皇帝,國不可一日無君private static final Emperor emperor = new Emperor();

    //防止刁民冒充皇帝

private Emperor(){}

    public static Emperor getEmperor(){
        return emperor;
    }

}

從上述程式碼中可以看到,類不能通過new的形式構造物件,只能用方法來獲取唯一的靜態物件。這種在宣告的時候就初始化的實現方式就叫做餓漢式。

(2)懶漢式

與餓漢式不同,懶漢式只有在第一次呼叫方式時才進行初始化。實現程式碼如下:

public class Singleton {
    
    private static Singleton instance;
    
    private Singleton(){}
    
    public static synchronized

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

懶漢式在方法中添加了synchronized關鍵字,可以在多執行緒情況下確保單例物件獨一無二。但即使已經被初始化,每次呼叫還會進行同步,會消耗不必要的資源,並且第一次載入時進行例項化會拖慢反應速度,因此懶漢式一般不建議使用。但懶漢式並非一無是處,如果一直沒有人用的話,就不會建立例項,則是節約空間。這是以時間換空間的實現方式,與餓漢式的以空間換時間各有所長。

還記得語文老師講課外文學知識題的“禪杖就是魯智深,戒刀就是武松,板斧就是李逵”的規律嗎?注意上面程式碼的getIntance()方法,實戰中見到getIntance()就是單例模式的準確率八九不離十。

(3)DCL式

Double Check Lock(以下簡稱DLC)實現單例模式既能夠在需要時才初始化物件,又能保證執行緒安全。程式碼如下:

public class Singleton {

    private static Singleton instance;

    private Singleton(){}

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

}

DCL可以保證無論何時讀取這個變數,都是讀到記憶體中最新的值,無論何時寫這個變數,都可以立即寫到記憶體中。DCL是目前單例模式最常見的實現方式。

(4)靜態內部類式

DCL儘管能完美解決資源消耗、同步多餘、執行緒不安全的問題,卻有低概率在併發場景比較複雜的情況下失效(少見於J2EE和Hadoop等場景,絕少見於Android場景)。因此在對效能要求極高的情況下我們可以採取靜態內部類式來實現單例模式。程式碼如下:

public class Singleton {

    private Singleton(){}

    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
    
    private static class SingletonHolder{
        private static final Singleton instance = new Singleton();
    }

}

靜態內部類式利用ClassLoader機制來保證初始化時僅有一個執行緒,不但不會造成效能損耗,還是天衣無縫的安全方式。

(5)單例模式的容器式管理

還是拿皇帝舉例子,天下可能有多個皇帝,一個軟體也可能有多個單例物件。舉唐玄宗李隆基和大燕皇帝安祿山的對立的例子不是太恰當,畢竟幾乎沒有史書認同安祿山是合法皇帝。我就舉一個大家耳熟能詳的例子——《三國演義》第80回講述了中國歷史上第一次同時存在兩位被後世的歷史學家認定為合法皇帝(曹丕和劉備)的局面,也是最著名的一次。我們先建立一個管理類:

public class EmperorManager {

    public static final String WEI = "魏";
    public static final String SHU = "蜀";
    public static final String WU = "吳";
    
    private static Map<String,Object> emperors = new HashMap<>();

    private EmperorManager(){}

    public static void ascendEmperor(String key,Object emperor){
        if (!emperors.containsKey(key)){
            emperors.put(key,emperor);
        }
    }
    
    public static Object getEmperor(String key){
        return emperors.get(key);
    }

}

然後就可以管理多個單例物件了:

//曹丕廢帝篡炎劉EmperorManager.ascendEmperor(EmperorManager.WEI,WeiEmperor.getEmperor());
//漢王正位續大統EmperorManager.ascendEmperor(EmperorManager.SHU,ShuEmperor.getEmperor());

幾年後,孫權登基,天下又有了新的皇帝,寫法以此類推。

Android原始碼中的單例模式

(1)Application

Application是Android中最典型,也是最常見的單例模式。使用者重寫Application類也只重寫一個。

(2)Activity

Activity在singleInstance啟動模式下只有一個例項,並且這個例項獨立執行在一個Task中,不允許有別的Activity存在,這也是一種單例模式。

(3)Service

Service用bindService()啟動之後,無論再啟動多少次,都只會呼叫onStartCommand()而不會再呼叫onCreate(),因為每次呼叫的Service都是同一物件。

(4)各種Manager

Android中有很多管理類,比如WindowManager、PowerManager、SensorManager、ActivityManager、StorageManager以及ServiceManager等等,這些管理類分別對某些資源進行操作,為了避免對同一資源的同時操作,也為了節約資源,都採取了單例模式。

(5)UID

在Picasso和Glide等框架流行起來之前,最常見的圖片載入框架非Universal-Image-Loader(以下簡稱UID)莫屬,UID的初始化方式如下:

//全域性初始化此配置ImageLoader.getInstance().init(config);

這裡又見到了熟悉的getIntance(),根據思維定式判定這是單例模式。

Android開發中如何利用單例模式

(1)當建立一個物件需要較多資源時,比如讀取配置或依賴較多其他物件時,可以用建立一個單例物件常駐記憶體的方式解決這個問題。

(2)當一個物件需要經常呼叫所以需要反覆建立、銷燬時,為了減少記憶體開支,可以用單例模式來減少建立、銷燬該物件的資源浪費。

(3)當需要對同一個資源進行操作時(例如File I/O),可以建立一個FileManager,這樣記憶體裡只有一個例項,避免了對同一個資源的同時操作。

需要注意的幾個問題

(1)單例模式必然有static修飾符,如果持有Activity的Context,很容易造成OOM,因此儘量使用Application的Context;此外有多少初學者在Activity銷燬時忘記銷燬視訊或地圖的單例物件而吃了大虧?

(2)在不需要獨一無二的物件的時候不要採用單例模式,譬如自定義控制元件就是最不適合單例模式的場景。