Android设计模式(一)-单例模式

原创 2017年03月26日 01:31:52

最近在看《Android源码设计模式解析与实战》这本书,发现里面还有对源码的一些分析,之前也没好好看过设计模式,就来做个笔记,跟着看一下。

简书地址

定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

使用场景

需要确保一个类只有一个实例的场景,避免产生多个对象小号过多的资源,或者是这个类只应该有一个实例。比如创建一个对象要消耗的资源过多,或者要访问IO和数据库等资源.
配置文件,工具类,线程池,缓存,日志对象等。

UML类图


角色介绍:
. Client——高层客户端
. Singleton——单例类

实现单例模式的关键点:
- 构造函数不对外开放,一般为Private。就是不允许外部通过new Singleton()来获取对象。
- 通过一个静态方法或枚举返回单例类对象,如getInstance()方法。
- 确保单例类的对象只有一个,尤其是在多线程的情况下。确保即使在多线程也能实现单例。
- 确保单例类对象在反序列化时不会重新构建对象。

实现方式

饿汉式

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

里面的对象是个静态对象,第一次声明的时候就会实现初始化。外部只能通过getInstance()获取到这个对象,实现单例。

懒汉式

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

这里的getInstance()方法加上了synchronized关键字,保证了多线程也能实现单例。

优点:
单例只有在使用时才会被实例化,一定程度上节约了资源。
缺点:
(1)单例在第一次加载时要及时进行实例化,反应稍慢
(2)每次调用getInstance()都要进行同步,造成不必要的同步开销。

所以一般不建议用。

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;
    }
}

这个getInstance方法中对mSingleton进行了两次判空:第一次是为了避免不必要的同步锁;第二层是为了在null的时候创建实例。
DCL失效:
在多线程下,假设A线程执行到mSingleton=new Singleton()的时候,CPU并不是一次性执行完这条语句的,因为这不是一个原子操作(指不会被线程调度机制打断的操作)。
举个例子:执行 Timer timer = new Timer(); 通过字节码文件可以看到这一行代码编译出来是这样的:

         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的实例分配内存
(2)调用Singleton的构造方法
(3)将mSingleton指向分配的内存空间(这个时候mSingleton才不为空)
由于Java编译器允许处理器乱序执行,所以上面的第二步第三步的执行顺序没法得到保证。执行顺序可能是1-2-3也可能是1-3-2。
当A线程执行顺序是1-3-2的时候,如果执行到了1-3,第2步还没执行的时候,如果B线程判断mSingleton==null的时候就会的发哦FALSE的结果,从而返回一个错误的单例。

优点:
资源利用率高,第一次执行getInstance的时候才会被实例化,效率高。
缺点:
第一册加载反应稍慢,而且有失败的可能,但是概率很小。

这种是用的最多的单例实现方式,大部分情况下都能保证单例。

静态内部类

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

当第一次加载Singleton的时候并不会初始化mSingleton,只有在第一次调用getInstance的时候才会加载SIngletonHolder类。
优点:
不仅能保证线程安全,也能保证单例的唯一性,也延迟了单例的实例化,比较推荐。

枚举单例

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

使用时可以通过
Singleton singleton = Singleton.INSTANCE;
来获取单例。
优点:
写法简单,而且默认线程安全,任何情况下都是一个单例。

特点:
上面的几种在有一种情况下会单例失效,出现重复创建对象,那就是反序列化。
反序列化的时候会调用一个readResolve()方法重新生成一个实例,所以上面的几种方式要解决这个问题需要加入以下方法,:

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;
    }
}

使用容器实现单例

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);
    }
}

在程序的开始,将许多要单例的对象放到一个容器里,用的时候根据key取得对应的单例对象。

Android源码中的单例模式

源码中的单例模式太多了,甚至有一个专门的单例的抽象类:

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;
        }
    }
}

我们经常通过context.getSystemService(String name)来获取一些系统服务,如在Activity中获取ActivityManager:

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

书中举例为LayoutInflater,平时获取方式为LayoutInflater.from(context),看下这个方法:

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;
    }

发现也是通过调用context.getSystemService(String name)获取的。

那么扎个单例是怎么实现的呢?顺着代码往上看吧。。
context.getSystemService(String name)直接点进去的话会进到

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

通过分析activity的启动流程可以知道,Context的功能的具体实现是在ContextImpl.java中,看具体实现代码:

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

然后继续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<?>>();
...
//静态代码块,第一次加载时执行,而且只会执行一次,保证了注册的服务的唯一性。
    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);
            }});
        ...
//还有很多服务注册
    }

  ...
//静态代码块中调用这个方法,把服务名和创建的服务对应放在容器中,实现单例。
      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;
    }
  ...
}

里面还不是直接拿到服务,而是调用了fetcher.getService(ctx)来获取服务。看看ServiceFetcher

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

这是个接口,看上面的静态代码块里面的方法发现注册服务的时候都是用的CachedServiceFetcher这个类:

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是获取一个数组:new Object[sServiceCacheSize];
//数组的长度就是构造方法中的那个变量,每注册一个服务,就会new一个对应的CachedServiceFetcher,然后数组长度就+1。第一次获取到这个数组肯定是个空数组
            final Object[] cache = ctx.mServiceCache;
            synchronized (cache) {
                // Fetch or create the service.
                Object service = cache[mCacheIndex];
//第一次获取这个服务的时候,数组是空的 ,所以service == null为TRUE。
                if (service == null) {
//调用注册时实现的createService方法,把生成的具体服务放在数组对应下标中,
//之后就直接从数组中获取了。实现了单例。
                    service = createService(ctx);
                    cache[mCacheIndex] = service;
                }
                return (T)service;
            }
        }
      //  在静态代码块中实现
        public abstract T createService(ContextImpl ctx);
    }

里面有个抽象方法,需要实例化的时候实现。在静态代码块中的方法都实现了这个createService(ContextImpl ctx)方法,并且返回了对应的服务。
附上部分注册服务的截图,截不下:

总结

优点:

  • 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁创建,或者创建或销毁时性能无法优化,单例模式的优势就很明显了。
  • 由于只生成一个实例,减少了系统性能开销。
  • 可以避免对资源的多重占用,如文件操作等。
  • 单例模式可以设置为全局的访问点,优化和共享资源访问。

缺点

  • 单例模式一般没有接口,拓展很困难,基本都要修改源代码。
  • 在Android中,如果单例模式持有Activity的Context,容易产生内存泄漏。所以尽量用ApplicationContext。

Android面试设计模式之单例模式

在面试的时候面试官会问我们常用的一些设计模式,这里先介绍一下单例模式。 为什么要使用单例模式 1.控制资源的使用,通过线程同步来控制资源的并发访问; 2.控制实例产生的数量,达到节约系统资源; 3...
  • qq_435559203
  • qq_435559203
  • 2016年09月12日 18:25
  • 558

设计模式——单例模式的几种实现方式

1.饿汉式:静态常量 这种方法非常的简单,因为单例的实例被声明成static和final变量了,在第一次加载类到内存中时就会初始化,所以会创建实例本身是线程安全的。public class Singl...
  • bingogirl
  • bingogirl
  • 2016年09月01日 00:03
  • 1169

设计模式:单例模式(Singleton)

单例模式在23个设计模式中算得上是最简单的一个了,也行你会有异议,那就换成“最简单之一”,这样就严谨了很多。   单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。   适用性:当类...
  • u013256816
  • u013256816
  • 2016年03月23日 21:37
  • 4734

java设计模式之单例模式(几种写法及比较)

概念:   java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例。   单例模式有以下特点:   1、单例类只能有一个实例。  ...
  • tolcf
  • tolcf
  • 2015年10月21日 22:56
  • 6142

Android开发设计模式之——单例模式

单例模式是设计模式中最常见也最简单的一种设计模式,保证了在程序中只有一个实例存在并且能全局的访问到。比如在android实际APP 开发中用到的 账号信息对象管理, 数据库对象(SQLiteOpenH...
  • Beyond0525
  • Beyond0525
  • 2014年04月02日 14:55
  • 45941

C++设计模式[二]单例模式

接着就是单例模式;个人理解就是把创建方法改为私有,然后再内部实例化,禁止外部创建对象。 何为单例模式,在GOF的《设计模式:可复用面向对象软件的基础》中是这样说的:保证一个类只有一个实例,并提供一个...
  • langb2014
  • langb2014
  • 2015年11月03日 16:11
  • 1001

Java设计模式透析之 —— 单例(Singleton)

写软件的时候经常需要用到打印日志功能,可以帮助你调试和定位问题,项目上线后还可以帮助你分析数据。但是Java原生带有的System.out.println()方法却很少在真正的项目开发中使用,甚至像f...
  • sinyu890807
  • sinyu890807
  • 2013年04月29日 11:36
  • 23617

设计模式(二)单例模式的七种写法

面试的时候,问到许多年轻的Android开发他所会的设计模式是什么,基本上都会提到单例模式,但是对单例模式也是一知半解,在Android开发中我们经常会运用单例模式,所以我们还是要更了解单例模式才对。...
  • itachi85
  • itachi85
  • 2016年01月17日 10:29
  • 23766

Java四种单例设计模式

Java中的四种单例模式单例模式是最容易理解的设计模式之一,介绍Java中单例模式的四种写法。1.饿汉式单例模式public class Singleton{ private static S...
  • twocold_2010
  • twocold_2010
  • 2016年11月20日 15:13
  • 176

单例模式(Singleton)- 最易懂的设计模式解析

前言今天我来全面总结一下Android开发中最常用的设计模式 - 单例模式。 关于设计模式的介绍,可以看下我之前写的:1分钟全面了解“设计模式” 目录1. 引入1.1 解决的是什么问题之前说过,设...
  • carson_ho
  • carson_ho
  • 2016年08月16日 17:15
  • 3977
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android设计模式(一)-单例模式
举报原因:
原因补充:

(最多只允许输入30个字)