標籤:android 設計模式 單例模式
Android中的設計模式-單例模式
單例模式算是比較常用的模式,在Java中如果想要一個JVM中只存在某個類的一個執行個體,就需要使用到單例模式,而只存在一個執行個體的需求一般是因為:
1,對象執行個體比較大和複雜,建立開銷很大。
2,只需要一個執行個體來維護整個功能的流程與互動。
例如Android中的電話應用啟動時,對於單卡單待的電話,只建立一個Phone對象,用來管理RIL,CallTracker,ServiceStateTracker等對象,手機中不存在第二個Phone對象去和RILC通訊。
類圖
單例的類圖很簡單,看起來也很簡單,只需要一個類只能得到一個執行個體即可,但是我覺得單例比其他建立型的模式要複雜的多。
如果想要建立一個類Singleton。正常來講只需要new Singleton()即可,但是如果想Singleton只存在一個執行個體,則不能採用這種方法來建立,因為每一次new都會產生一個新的執行個體:
1,為了不能使用new建立,就要把建構函式變成private的;
2, 為了只有一個執行個體,就需要Singleton本身去維護這個執行個體,於是類中需要定義一個Singleton instance,顯然它應該是private的,因為它是類的執行個體,不是對象的執行個體,所以它還應該是靜態;
3,因為不能new,為了能夠有方法得到Singleton的執行個體,就得通過一個靜態方法返回執行個體,比如public static Singleton getInstance(),在內部如果instance是null的則建立,否則返回instance即可,
說了這麼多其實需要注意的東西還是蠻多的,根據上面的分析,已經能夠建立出一個最簡單單例模式了。
普通餓漢單例模式
public class Singleton {private static Singleton instance = new Singleton();private Singleton() {// Nothing}public static Singleton getInstance(){return instance;}public void say(){System.out.println("I am singleton");}}
餓漢單例會在類裝載時就執行個體化。好處就是由於classloder機制,保證當一個類被載入的時候,這個類的載入是線程互斥的,而餓漢單例的靜態instance直接建立一個執行個體,在載入的時候就能安全執行緒的獲得執行個體。從而避免了安全執行緒問題。
劣勢就是執行個體在裝載的時候就會浪費資源和時間去執行個體化。雖然大多數時候都是在調用getInstance時才會裝載,不過也沒法保證是否有其他方法會使用到instance而導致建立執行個體。
惰性載入單例模式
這個Singleton單例類在被載入的時候就會建立執行個體,為了讓它在使用的時候才建立對象所以把它設為null。讓它在getInstace的時候再建立執行個體,也就是惰性載入。這裡也叫懶漢式單例。
public class lazySingleton {private static lazySingleton instance = null;private lazySingleton() {// TODO Auto-generated constructor stub}public static lazySingleton getInstace() {if (instance==null){instance =new lazySingleton();}return instance;}public void say(){System.out.println("I am lazySingleton");}}
加鎖單例模式
前面的懶漢例子用在單線程中不會出現問題,但是如果用在單線程中就會出現問題。如果AB兩個線程都通過getInstace去擷取單例的執行個體,因為沒法保證getInstace方法會在一個線程中一直執行完再執行另一個線程,如果兩個線程都判定instance位null,則有可能都會進入new語句建立執行個體。
為了保證同一時刻只有一個線程能夠執行getInstance,就需要對方法加鎖,或者在方法內部使用對象鎖,但是注意要把鎖放在if(instance == null) 判斷的外面,否則還是可能出現同時判定true的情況。
public class SyncSingleton {private static SyncSingleton instance = null;private SyncSingleton() {// TODO Auto-generated constructor stub}public static synchronized SyncSingleton getInstance(){if (instance == null){instance = new SyncSingleton();}return instance;}}
或者
public class SyncSingleton { private static SyncSingleton instance = null; private static final Object classLock = SyncSingleton.class; private SyncSingleton(){} public SyncSingleton getInstance(){ synchronized(classLock){ if(instance == null) instance = new SyncSingleton(); return instance; } } }
雙重檢查加鎖單例
上面的問題解決了多線程的問題,但是也會帶來效能的問題,因為每次調用getInstance,都會進行同步,但實際上,如果instance執行個體已經建立,那直接返回instance執行個體就好,這裡是不用加鎖的,只有建立執行個體的情況才需要同步鎖,但是前面也說到了,建立的時候要把鎖放在if(instance == null) 判斷的外面,否則還是可能出現同時判定true的情況。所以就有了下面的雙重檢查的單例模式
public class EffectiveSingleton { private volatile static EffectiveSingleton instance = null; private EffectiveSingleton() { // TODO Auto-generated constructor stub } public static EffectiveSingleton getInstance() { if (instance == null) { // 首先判斷是否已經建立執行個體,如果已經建立,直接返回,效率高 synchronized (EffectiveSingleton.class) {// 如果沒有建立,然後再同步,並在同步塊內再初始化。注意要再次判斷是否已執行個體化 if (instance == null) { instance = new EffectiveSingleton(); } } } return instance; }}
這樣當執行個體初始化已經完成的情況,每次getInstance直接返回即可,不再需要同步鎖。
當第一次調用getInstance時,則通過synchronized塊包裹的代碼部分保證不會多次調用建立執行個體。
第二個if (instance == null) 條件判斷是為了第一個if (instance == null) 是給已經存在執行個體的情況用的,AB線程還是有可能都通過第一個條件判斷的。這就需要在同步塊內,一定要有一個條件判斷。
雙重檢查加鎖單例模式很好地解決了加鎖單例的效能問題。
使用靜態內部類的單例模式
靜態內部類也叫嵌套類,用這個名字給他定義是更加形象的。意思是說內部類和外部類的關係只是層次嵌套關係,所以只是在建立類檔案的時候類檔案名稱是如下形式:outer$inner.java,在使用方面完全和兩個普通類一樣。
在餓漢單例的基礎上,把instance = new Singleton()外用一個叫做SingletonHolder 的靜態內部類封裝一下。
這樣即使在Singleton類載入的時候,也不會導致靜態內部類SingletonHolder 的載入,只有在使用到SingletonHolder 的時候,才會載入。
同時由於類的載入的機制的互斥性,保證建立執行個體時候的執行緒安全性。
class Singleton { private static class SingletonHolder { private static final Singleton instance = new Singleton(); } private Singleton() { } public static final Singleton getInstance() { return SingletonHolder.instance; }}
Android中的單例
通過PhoneFactory建立Phone對象的例子,截取部分如下:
public class PhoneFactory { static private Phone sProxyPhone = null; ...... public static void makeDefaultPhone(Context context) { synchronized(Phone.class) { if (!sMadeDefaults) { ... sProxyPhone = new PhoneProxy(new GSMPhone(context, sCommandsInterface, sPhoneNotifier)); ... sMadeDefaults = true; } } }
可以看出這個例子符合上面的加鎖單例模式,雖然不是採用的雙重判斷的方式來增加效率,但是因為PhoneFactory的makeDefaultPhone基本沒有多線程使用方式,只有在Phone應用啟動的情況下調用一起。
並且PhoneFactory通過makeDefaultPhone來建立執行個體,但是卻使用getDefaultPhone來擷取執行個體,也就不存在執行個體已經存在的情況下,還進入同步塊進行判斷的情況。
Android中的設計模式-單例模式