《Android源码设计模式》读书笔记——单例模式

定义:

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

使用场景:

确保某个类只有一个对象,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有一个。例如创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源,这时就要考虑使用单例模式。 《Effective Java》中提到要不要使用单例取决于,这个类的所有实例在功能上都是相互等价的。

关键点:

1,构造函数不对外开放,一般为private
2,通过一个静态方法或者枚举返回单例类对象
3,确保单例类的对象有且只有一个,尤其是在多线程环境下
4,确保单例类对象在反序列化时不会重新构建对象

实现方式:

饿汉模式:

public class Singleton {

    private static final Singleton sInstance = new Singleton();

    private Singleton() {

    public static Singleton getInstance() {
        return sInstance;
    }

}

优缺点:静态对象声明时就已经初始化。加载类比较慢,但是运行时获取对象的速度比较快,而且是线程安全的;

懒汉模式

public class Singleton {

    private static Singleton sInstance;

    private Singleton() {

    public static synchronized Singleton getInstance() {
        if(sInstance== null)
            sInstance= new Singleton();
        return sInstance;
    }

}

优缺点:优点是对象只有在使用时才会被实例化,在一定程度上节约了资源。缺点是第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance都进行同步造成不必要的同步开销。这种模式一般不建议使用。

双检锁(Double Check Lock)模式

public class Singleton {

    private volatile static Singleton sInstance;

    private Singleton() {

    }

    public static Singleton getInstance() {
        // 避免不必要的同步
        if (sInstance == null) {
            synchronized (Singleton.class) {
                // sInstance为null的情况下创建实例
                if (sInstance == null) {
                    sInstance = new Singleton();
                }
            }
        }
        return sInstance;
    }

}

这种方式偶尔会有DCL失效的问题,假设线程A执行到 sInstance = new Singleton() 语句,这里看起来是一句代码,但实际上并不是一个原子操作,这句代码最终会被编译成多条汇编指令,它大致做了3件事情:

  1. 给Singleton的实例分配内存
  2. 调用Singleton的构造方法,初始化成员字段
  3. 将sInstance对象指向分配的内存空间(此时sInstance就不是null了)

由于Java编译器允许处理器乱序执行,以及JDK1.5之前的JMM(Java Memory Model,Java内存模型)中的Cache、寄存器到主内存回写顺序的规定,上面2和3的顺序是无法保证的。执行的顺序可能是1-2-3也能是1-3-2。如果是后者,并且在3执行之后,2执行之前,被切换到B线程上,这时候sInstance因为已经在线程A内执行了3,sInstance已经不为空了,所以线程B直接取走sInstance使用就会出错,这就是DCL失效问题。

JDK1.5之后,sun官方重新定义了volatile关键字,如果是JDK1.5或之后的版本,上面的代码可以保证原子性和可见性。就可以放心使用这种方式来实现单例。

优缺点:第一次执行getInstance()时对象才会被实例化,效率高。但是第一次加载时反应稍慢,JDK1.5之前可能会失败。除非在并发场景比较复杂并且JDK1.5之前使用,否则一般能够满足需求。

静态内部类模式

public class Singleton {

    private Singleton() {}

    public static Singleton getInstance() {
        return SingletonHolder.sInstance;
    }

    private static class SingletonHolder {
        private static final Singleton sInstance = new Singleton();
    }

}

优缺点:加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance()时才会加载SingletonHolder类,初始化sInstance对象。不仅能够保证线程安全,也能够保证单例对象的唯一,同时也延迟了对象的实例化,是推荐的实现方式。

枚举单例

public enum Singleton {

    INSTANCE;

}

之前几种模式,通过序列化写入磁盘,再反序列化读回来,就可以获取到一个新的实例。反序列化操作提供了一个很特别的钩子函数readResolve(),这个方法可以让开发人员控制对象的反序列化。上面几种方式如果要避免在被反序列化时重新生成对象,那么需要加入以下方法:

    private Object readResolve() throws ObjectStreamException {
        return sInstance;
    }

序列化意味着一个对象的生存周期并不取决于程序是否正在执行。对于Serializable,对象完全以它存储的二进制位为基础来构造,而不调用构造器。而对于一个Externalizable对象,所有普通的默认构造器都会被调用(包括在字段定义时的初始化),然后调用readExternal()。《忘记是哪本书了》 Externalizable 继承自 java.io.Serializable。通常我们不需要实现Serializable接口,所以一般也不会有序列化的问题。虽然所有的枚举类都实现了Serializable接口,通过反序列化返回的依然是同一个对象。

以上几种方式使用反射同样可以拿到一个新的对象,枚举同样可以避开这个问题,在获取构造方法的时候就会抛出java.lang.NoSuchMethodException的异常,保证实例的唯一性。

优缺点:写法简单,即使反序列化也不会生成新的实例。

使用容器实现单例模式

// 注册服务
registerService(Context.WINDOW_SERVICE, WindowManager.class, new CachedServiceFetcher<WindowManager>() {
    @Override
    public WindowManager createService(ContextImpl ctx) {
        return new WindowManagerImpl(ctx);
    }}); 

// 加入到map中
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;
}

// CachedServiceFetcher实现了ServiceFetcher接口
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) {
        final Object[] cache = ctx.mServiceCache;
        synchronized (cache) {
            // Fetch or create the service.
            Object service = cache[mCacheIndex];
            if (service == null) {
                try {
                    service = createService(ctx);
                    cache[mCacheIndex] = service;
                } catch (ServiceNotFoundException e) {
                    onServiceNotFound(e);
                }
            }
            return (T)service;
        }
    }

    public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
}

android中各种系统服务的实例都是使用这种方式获取的实例对象,注册服务的时候以name为key,以一个CachedServiceFetcher对象为value存入到map中,在获取服务的时候,通过name拿到CachedServiceFetcher对象,调用getService()方法,fetcher.getService(ctx)最后会调用CachedServiceFetcher的createService方法创建实例然后缓存起来,下次直接返回。

优缺点:这种方式可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

猜你喜欢

转载自blog.csdn.net/jthou20121212/article/details/81714011