作者:ivm
之前做android 项目时,用的最多的就是设计模式,就是单例模式,用的时候,心里总有些疑问。今天呢,看了《Android 源码设计模式 解析与实战》,把自己的总结分享一下。
首先什么是单例模式?
单例模式:使用时,单例的对象必须保证只有一个实例存在,不予许自由构造对象。
那么单例模式的使用场景有哪些?
确保某个类有且只有一个实例,而且自行实例化并向整个系统提供这个实例
如何“单例”?
1.构造函数不对外开放,一般为private 。
2.通过一个静态方法或者枚举返回单例类对象。
3.确保单例类的对象在反序列化时不会重新构建对象。(下文会讲)
4.确保单例类对象有且只有一个,尤其是多线程下。
单例模式的分类
1.饿汉式
2.懒汉式
懒汉式与饿汉式的区别:
懒汉式:实名一个静态对象,并且在用户第一次调用getInstance 时进行初始化,优点:单例只有在使用时才会被实例化,一定程度上节约了资源。缺点:是第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance 都进行同步,造成不必要的同步开销。一般不建议这么用。
饿汉式:是在声明静态对象时就已经初始化。
3.Double Check Lock (DCL)实现单例
DCL 介绍
DCL 在getInstance 方法中 对instance 进行两次判空:相信很多人对此都有些疑惑。为什么要判断两次,第一个判空是为了避免不必要的同步,第二层判断是为了在null 情况下创建实例。instance=new Singleton(); 语句看起来是有代码,单实际是一个原子操作,最终会被编译成多条汇编指令,大致做了三件事:
1.给Singleton 分配内存
2.调用Singleton 的构造函数,初始化成员字段
3.将instance 对象指向分配的内存空间(此时instance 就不是null 了)
但是jdk 1.5 以后java 编译器允许乱序执行 。所以执行顺序可能是1-3-2 或者 1-2-3.如果是前者先执行3 的话 切换到其他线程,instance 此时 已经是非空了,此线程就会直接取走instance ,直接使用,这样就回出错。DCL 失效。解决方法 SUN 官方已经给我们了。将instance 定义成 privatevolatilestatic Singleton instance =null: 即可
DCL 的优点,资源利用率高,第一次执行getInstance 时才会被实例化,效率高。缺点:第一次加载反应慢,也由于java 内存 模型的原因偶尔会失败,在高并发环境下,有一定缺陷,虽然发生概率很小。(很常用)
4.静态内部类单例模式
加载singleton 类时不会初始化instance 只有在调用getInstance 方法时,才会导致instance 被初始化,这个方法不仅能够确保线程安全,也能够保证 单例对象的唯一性,同时也延迟了单例的实例化,是推荐使用的单例模式实现方式。
5.枚举单例
它在任何情况下都是单例的,也是最简单的。在上述的几种单例模式下,都会有一种情况,它们会出现重新创建对象的情况,那就是反序列化。
要杜绝单例对象在反序列化时重新生成对象,那么必须加入如下方法:
但是枚举就不必要加这个方法,因为反序列话它也不会生成新的实例。
6.使用容器模式实现单例
将众多单例模式类型注入到一个统一的管理类中,在使用时根据key 对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。