個人總結學習和研究,部分內容參考《Android源碼設計模式解析與實戰》一書~~
一. 定義: 確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。
也就是說,單例要滿足3點:
1、單例類只能有一個實例。
2、單例類必須自己創建自己的唯一實例。(構造函數私有化,防止外部程序通過new來構造)。
3、單例類必須給其他對象提供這一實例。(暴露公有靜態方法或者通過枚舉返回單例類對象)。
二. 使用場景: 確保某個類有且只有一個對象的場景,避免產生多個對象消耗過多的資源。比如說在一個應用中,應該只有一個ImageLoader實例,這個Imageloader中含有線程池、緩存系統、網絡請求等,比較消耗資源。還比如訪問IO和數據庫等資源,就要考慮使用單例模式。
android源碼中也有很多地方用了單例模式,比如說輸入法管理者InputMethodManager,比如一個應用只有一個Application對象等。
三. 實現方式:
1、餓漢式單例
特點:聲明靜態對象的時候進行初始化靜態對象,以后不再改變,天生是線程安全的。弊端:消耗資源。
public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { retun instance; } }
2、懶漢式單例
特點:聲明靜態對象,並在第一次調用getInstance時進行初始化。優勢:延遲加載。弊端:線程不安全。
public class Singleton { private static Singleton instance = null; private Singleton() { } public static Singleton getInstance () { if (instance == null) { instatnce = new Singleton(); } return instance; } }
懶漢式加鎖
getInstance()靜態方法添加synchronized關鍵字,即同步方法保證線程安全。 弊端:造成不必要的同步開銷。具體代碼如下:
public class Singleton { private static Singleton instance = null; private Singleton() { } public static synchronized Singleton getInstance () { if (instance == null) { instatnce = new Singleton(); } return instance; } }
3、雙重檢查鎖定(Double CheckLock)實現單例
特點:延遲加載,解決了多余的同步,線程安全。
兩次判空,第一層是為了避免不必要的同步。 第二層是為了在instance為null的情況下創建實例。
public class Singleton { private static Singleton instance = null; private Singleton () { } public static Singleton getInstance () { // If already initialized, no need to get lock everytime. if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance ; } }
4、靜態內部類單例
特點:延遲加載,線程安全。 利用Java虛擬機加載類的特性實現延遲加載和線程安全, 推薦使用的單例實現方式。
public class Singleton() { private Singleton() { } public static Singleton getInstance () { return SingletonHolder.instance; } /** * 靜態內部類,只有在裝載該內部類時才會去創建單例對象 */ private static class SingletonHolder { private static final Singleton instance = new Singleton(); } }
5、枚舉單例
特點:寫法簡單,線程簡單,反序列化也不會重新創建對象。(前面四種情況在反序列化中均會生成新的實例)。
枚舉單例雖然在Effective Java中推薦使用,但是在Android平台上卻是不被推薦的。
Android官方的Training課程中明確指出:
Enums ofter require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
public enum Singleton { // 定義一個枚舉的元素,它就是Singleton的一個實例 INSTANCE; public void doSomethig() { // TODO: } }
枚舉在java中與普通的類一樣的,不僅能夠有字段,還能夠有自己的方法。
最重要的是默認枚舉實例的創建是線程安全的,並且在任何情況下都是一個單例。 使用方法如下:
Singleton singleton = Singleton.INSTANCE; singleton.doSomething();
6、使用容器實現單例
腦洞打開,再來看看一種另類的實現方式。維護一個統一的管理類,注入多種單例類型,在使用時根據key獲取對應類型的單例對象。
public class SingletonManager { private static Map<String, Object> objMap = new HashMap<>(); private Singleton() { } public static void addSingleton(String key, Object instance) { if (!objMap.containsKey(key)) { objMap.put(key, instance); } } public static getSingleton(String key) { return objMap.get(key); } }
總結: 選擇哪種實現方式取決於項目本身,如是否是復雜的並發環境、JDK版本是否過低,單例對象的資源消耗等。一般而言,手機客戶端通常沒有高並發的情況,所以具體選擇哪種實現方式並不會有太大的影響。個人一般使用靜態內部類的實現方式。
注意: 單例對象如果持有Context,很容易引發內存泄露,需要注意傳遞給單例對象的Context最好是Application Context。