Android 单例模式

来源:互联网 发布:js trigger 编辑:程序博客网 时间:2024/05/16 17:27

看了Android设计模式和鸿洋大神和stormzhang的博客后,感觉对Android最常用的设计模式 - 单例模式的理解又深了一步,准备记录下来。

1. 饿汉式

private static Singleton instance = new Singleton();private Singleton(){}public static Singleton getInstance(){return instance;}

饿汉式是最简单,也是最粗暴的单例写法,这种方式的单例对象在应用初始化的时候就已经加载了,如果单例对象加载快并且占用内存少的话,这种方式还挺合适。但是如果单例对象加载慢,占用内存大,而应用对启动速度也有要求,或者这个单例对象只有在特定场景下才使用,饿汉式就不推荐使用了。为了解决延迟加载的问题,就有了懒汉式。


2. 懒汉式

private static Singleton instance = null;private Singleton(){}public static Singleton getInstance(){if (null == instance) {instance = new Singleton();}return instance;}

懒汉式和饿汉式最大的区别就是需要的时候再进行单例的初始化操作,比如某个单例用的次数不是很多,但是这个单例提供的功能又非常复杂,而且加载和初始化要消耗大量的资源,这个时候使用懒汉式就是非常不错的选择。


3. Doble Check Lock(DCL) 双重加锁

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

DCL方式是在懒汉式的基础上,为了解决多线程下的单例模式,线程安全,还有考虑到加锁的效率问题,这种方式是比较推荐的写法。当然其实这种方法还是有坑的,说这个坑之前我们要先来看看volatile这个关键字。其实这个关键字有两层语义。第一层语义相信大家都比较熟悉,就是可见性。可见性指的是在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其它线程的读取操作中。顺便一提,工作内存和主内存可以近似理解为实际电脑中的高速缓存和主存,工作内存是线程独享的,主存是线程共享的。volatile的第二层语义是禁止指令重排序优化。大家知道我们写的代码(尤其是多线程代码),由于编译器优化,在实际执行的时候可能与我们编写的顺序不同。编译器只保证程序执行结果与源代码相同,却不保证实际指令的顺序与源代码相同。这在单线程看起来没什么问题,然而一旦引入多线程,这种乱序就可能导致严重问题。volatile关键字就可以从语义上解决这个问题。

注意,前面反复提到“从语义上讲是没有问题的”,但是很不幸,禁止指令重排优化这条语义直到jdk1.5以后才能正确工作。此前的JDK中即使将变量声明为volatile也无法完全避免重排序所导致的问题。所以,在jdk1.5版本前,双重检查锁形式的单例模式是无法保证线程安全的。


4. 静态内部类单例模式

private Singleton(){}public static Singleton getInstance(){return InstanceHolder.instance;}public static class InstanceHolder{public static Singleton instance = new Singleton();}

这种方式是Android比较推荐的写法,既实现了延迟加载,还确保了线程安全和单例对象的唯一性。


5. 枚举类单例模式

public enum Singleton{instance;}
不管饿汉式、懒汉式、双重校验锁还是静态内部类都有两个共同的缺点:
1.都需要额外的工作(Serializable、transient、readResolve())来实现序列化,否则每次反序列化一个序列化的对象实例时都会创建一个新的实例。
2.可能会有人使用反射强行调用我们的私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。

枚举类单例解决了问题,但是枚举类型在内存占用上是静态的两倍,如果你的应用出现性能问题,这个地方就可以优化了。


好了,这些就是常用的五种单例模式,我们要根据我们的使用场景,jdk版本,是否复杂的并发场景,去选择最适合的单例模式。单例的核心原理就是把构造函数私有化并且通过静态方法获取一个唯一的实例,在使用的过程中,注意单例的三个要点:

延迟加载

线程安全

防止序列化


最后,要特别感谢stormzhang 大神!













1 0
原创粉丝点击