Android設(shè)計(jì)模式(一)-單例模式

目錄

  1. 定義
  2. 使用場(chǎng)景
  3. UML類圖
  4. 實(shí)現(xiàn)方式
  5. 餓漢式
  6. 懶漢式
  7. Double Check LockDCL雙重檢查鎖
  8. 靜態(tài)內(nèi)部類
  9. 枚舉單例
  10. 使用容器實(shí)現(xiàn)單例
  11. Android源碼中的單例模式
  12. 總結(jié)
  13. 優(yōu)點(diǎn)
  14. 缺點(diǎn)

博客地址
最近在看《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》這本書,發(fā)現(xiàn)里面還有對(duì)源碼的一些分析,之前也沒好好看過設(shè)計(jì)模式,就來做個(gè)筆記,跟著看一下。
包括設(shè)計(jì)模式和一些源碼分析。

定義

確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。

使用場(chǎng)景

需要確保一個(gè)類只有一個(gè)實(shí)例的場(chǎng)景,避免產(chǎn)生多個(gè)對(duì)象小號(hào)過多的資源,或者是這個(gè)類只應(yīng)該有一個(gè)實(shí)例。比如創(chuàng)建一個(gè)對(duì)象要消耗的資源過多,或者要訪問IO和數(shù)據(jù)庫等資源.
配置文件,工具類,線程池,緩存,日志對(duì)象等。

UML類圖


角色介紹:
. Client——高層客戶端
. Singleton——單例類

實(shí)現(xiàn)單例模式的關(guān)鍵點(diǎn):

  • 構(gòu)造函數(shù)不對(duì)外開放,一般為Private。就是不允許外部通過new Singleton()來獲取對(duì)象。
  • 通過一個(gè)靜態(tài)方法或枚舉返回單例類對(duì)象,如getInstance()方法。
  • 確保單例類的對(duì)象只有一個(gè),尤其是在多線程的情況下。確保即使在多線程也能實(shí)現(xiàn)單例。
  • 確保單例類對(duì)象在反序列化時(shí)不會(huì)重新構(gòu)建對(duì)象。

實(shí)現(xiàn)方式

餓漢式

public class Singleton {
    private static final Singleton mSingleton = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return mSingleton;
    }
}

里面的對(duì)象是個(gè)靜態(tài)對(duì)象,第一次聲明的時(shí)候就會(huì)實(shí)現(xiàn)初始化。外部只能通過getInstance()獲取到這個(gè)對(duì)象,實(shí)現(xiàn)單例。

懶漢式

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

這里的getInstance()方法加上了synchronized關(guān)鍵字,保證了多線程也能實(shí)現(xiàn)單例。

優(yōu)點(diǎn):
單例只有在使用時(shí)才會(huì)被實(shí)例化,一定程度上節(jié)約了資源。
缺點(diǎn):
(1)單例在第一次加載時(shí)要及時(shí)進(jìn)行實(shí)例化,反應(yīng)稍慢
(2)每次調(diào)用getInstance()都要進(jìn)行同步,造成不必要的同步開銷。

所以一般不建議用。

Double Check Lock(DCL)雙重檢查鎖

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

這個(gè)getInstance方法中對(duì)mSingleton進(jìn)行了兩次判空:第一次是為了避免不必要的同步鎖;第二層是為了在null的時(shí)候創(chuàng)建實(shí)例。
DCL失效:
在多線程下,假設(shè)A線程執(zhí)行到mSingleton=new Singleton()的時(shí)候,CPU并不是一次性執(zhí)行完這條語句的,因?yàn)檫@不是一個(gè)原子操作(指不會(huì)被線程調(diào)度機(jī)制打斷的操作)。
舉個(gè)例子:執(zhí)行 Timer timer = new Timer(); 通過字節(jié)碼文件可以看到這一行代碼編譯出來是這樣的:

         0: new           #2                  // class java/util/Timer
         3: dup
         4: invokespecial #3                  // Method java/util/Timer."<init>":()V
         7: astore_1
         8: return

所以mSingleton=new Singleton()大致做了三件事:
(1)給Singleton的實(shí)例分配內(nèi)存
(2)調(diào)用Singleton的構(gòu)造方法
(3)將mSingleton指向分配的內(nèi)存空間(這個(gè)時(shí)候mSingleton才不為空)
由于Java編譯器允許處理器亂序執(zhí)行,所以上面的第二步第三步的執(zhí)行順序沒法得到保證。執(zhí)行順序可能是1-2-3也可能是1-3-2。
當(dāng)A線程執(zhí)行順序是1-3-2的時(shí)候,如果執(zhí)行到了1-3,第2步還沒執(zhí)行的時(shí)候,如果B線程判斷mSingleton==null的時(shí)候就會(huì)的發(fā)哦FALSE的結(jié)果,從而返回一個(gè)錯(cuò)誤的單例。

優(yōu)點(diǎn):
資源利用率高,第一次執(zhí)行g(shù)etInstance的時(shí)候才會(huì)被實(shí)例化,效率高。
缺點(diǎn):
第一冊(cè)加載反應(yīng)稍慢,而且有失敗的可能,但是概率很小。

這種是用的最多的單例實(shí)現(xiàn)方式,大部分情況下都能保證單例。

靜態(tài)內(nèi)部類

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

當(dāng)?shù)谝淮渭虞dSingleton的時(shí)候并不會(huì)初始化mSingleton,只有在第一次調(diào)用getInstance的時(shí)候才會(huì)加載SIngletonHolder類。
優(yōu)點(diǎn):
不僅能保證線程安全,也能保證單例的唯一性,也延遲了單例的實(shí)例化,比較推薦。

枚舉單例

public enum Singleton{
    INSTANCE;
    public void doThing(){
        System.out.println(this.hashCode());
    }
}

使用時(shí)可以通過Singleton singleton = Singleton.INSTANCE;來獲取單例。
優(yōu)點(diǎn):
寫法簡單,而且默認(rèn)線程安全,任何情況下都是一個(gè)單例。

特點(diǎn):
上面的幾種在有一種情況下會(huì)單例失效,出現(xiàn)重復(fù)創(chuàng)建對(duì)象,那就是反序列化。
反序列化的時(shí)候會(huì)調(diào)用一個(gè)readResolve()方法重新生成一個(gè)實(shí)例,所以上面的幾種方式要解決這個(gè)問題需要加入以下方法,:

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

    private Object readRedolve() throws ObjectStreamException{
        return SingletonHolder.mSingleton;
    }
}

使用容器實(shí)現(xiàn)單例

public class SingletonManager {
    private static Map<String,Objects> objMap = new HashMap<>();
    private SingletonManager(){}
    public static void registerService(String key,Object obj){
        if (!objMap.containsKey(key)){
            objMap.put(key,obj);
        }
    }
    public static Object getService(String key){
        return objMap.get(key);
    }
}

在程序的開始,將許多要單例的對(duì)象放到一個(gè)容器里,用的時(shí)候根據(jù)key取得對(duì)應(yīng)的單例對(duì)象。

Android源碼中的單例模式

源碼中的單例模式太多了,甚至有一個(gè)專門的單例的抽象類:

package android.util;
public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

我們經(jīng)常通過context.getSystemService(String name)來獲取一些系統(tǒng)服務(wù),如在Activity中獲取ActivityManager:

ActivityManager  mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);  

書中舉例為LayoutInflater,平時(shí)獲取方式為LayoutInflater.from(context),看下這個(gè)方法:

package android.view;
public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

發(fā)現(xiàn)也是通過調(diào)用context.getSystemService(String name)獲取的。

那么扎個(gè)單例是怎么實(shí)現(xiàn)的呢?順著代碼往上看吧。。
context.getSystemService(String name)直接點(diǎn)進(jìn)去的話會(huì)進(jìn)到

package android.content;
public abstract class Context {
...
    public abstract Object getSystemService(@ServiceName @NonNull String name);
...
}

通過分析activity的啟動(dòng)流程可以知道,Context的功能的具體實(shí)現(xiàn)是在ContextImpl.java中,看具體實(shí)現(xiàn)代碼:

package android.app;
class ContextImpl extends Context {
...
    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
}
...

然后繼續(xù)SystemServiceRegistry.getSystemService(this, name):

package android.app;
final class SystemServiceRegistry {
    ...
//用來getSystemService的容器,里面存放的是ServiceFetcher<?>
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
...
//靜態(tài)代碼塊,第一次加載時(shí)執(zhí)行,而且只會(huì)執(zhí)行一次,保證了注冊(cè)的服務(wù)的唯一性。
    static {
        registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
                new CachedServiceFetcher<AccessibilityManager>() {
            @Override
            public AccessibilityManager createService(ContextImpl ctx) {
                return AccessibilityManager.getInstance(ctx);
            }});
        registerService(Context.DOWNLOAD_SERVICE, DownloadManager.class,
                new CachedServiceFetcher<DownloadManager>() {
            @Override
            public DownloadManager createService(ContextImpl ctx) {
                return new DownloadManager(ctx);
            }});
        ...
//還有很多服務(wù)注冊(cè)
    }
    
  ...
//靜態(tài)代碼塊中調(diào)用這個(gè)方法,把服務(wù)名和創(chuàng)建的服務(wù)對(duì)應(yīng)放在容器中,實(shí)現(xiàn)單例。
      private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }
  ...
  public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
  ...
}

里面還不是直接拿到服務(wù),而是調(diào)用了fetcher.getService(ctx)來獲取服務(wù)。看看ServiceFetcher<?>:

static abstract interface ServiceFetcher<T> {
        T getService(ContextImpl ctx);
    }

這是個(gè)接口,看上面的靜態(tài)代碼塊里面的方法發(fā)現(xiàn)注冊(cè)服務(wù)的時(shí)候都是用的CachedServiceFetcher這個(gè)類:

static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
        private final int mCacheIndex;

        public CachedServiceFetcher() {
            mCacheIndex = sServiceCacheSize++;
        }

        @Override
        @SuppressWarnings("unchecked")
        public final T getService(ContextImpl ctx) {
//ctx.mServiceCache是獲取一個(gè)數(shù)組:new Object[sServiceCacheSize];
//數(shù)組的長度就是構(gòu)造方法中的那個(gè)變量,每注冊(cè)一個(gè)服務(wù),就會(huì)new一個(gè)對(duì)應(yīng)的CachedServiceFetcher,然后數(shù)組長度就+1。第一次獲取到這個(gè)數(shù)組肯定是個(gè)空數(shù)組
            final Object[] cache = ctx.mServiceCache;
            synchronized (cache) {
                // Fetch or create the service.
                Object service = cache[mCacheIndex];
//第一次獲取這個(gè)服務(wù)的時(shí)候,數(shù)組是空的 ,所以service == null為TRUE。
                if (service == null) {
//調(diào)用注冊(cè)時(shí)實(shí)現(xiàn)的createService方法,把生成的具體服務(wù)放在數(shù)組對(duì)應(yīng)下標(biāo)中,
//之后就直接從數(shù)組中獲取了。實(shí)現(xiàn)了單例。
                    service = createService(ctx);
                    cache[mCacheIndex] = service;
                }
                return (T)service;
            }
        }
      //  在靜態(tài)代碼塊中實(shí)現(xiàn)
        public abstract T createService(ContextImpl ctx);
    }

里面有個(gè)抽象方法,需要實(shí)例化的時(shí)候?qū)崿F(xiàn)。在靜態(tài)代碼塊中的方法都實(shí)現(xiàn)了這個(gè)createService(ContextImpl ctx)方法,并且返回了對(duì)應(yīng)的服務(wù)。
附上部分注冊(cè)服務(wù)的截圖,截不下:


總結(jié)

優(yōu)點(diǎn):

  • 由于單例模式在內(nèi)存中只有一個(gè)實(shí)例,減少了內(nèi)存開支,特別是一個(gè)對(duì)象需要頻繁創(chuàng)建,或者創(chuàng)建或銷毀時(shí)性能無法優(yōu)化,單例模式的優(yōu)勢(shì)就很明顯了。
  • 由于只生成一個(gè)實(shí)例,減少了系統(tǒng)性能開銷。
  • 可以避免對(duì)資源的多重占用,如文件操作等。
  • 單例模式可以設(shè)置為全局的訪問點(diǎn),優(yōu)化和共享資源訪問。

缺點(diǎn)

  • 單例模式一般沒有接口,拓展很困難,基本都要修改源代碼。
  • 在Android中,如果單例模式持有Activity的Context,容易產(chǎn)生內(nèi)存泄漏。所以盡量用ApplicationContext。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容