应用最广的模式--单例模式
来源:互联网 发布:上海德颐网络正规吗 编辑:程序博客网 时间:2024/05/17 04:05
本文出自博客Vander丶CSDN博客,如需转载请标明出处,尊重原创谢谢
博客地址:http://blog.csdn.net/l540675759/article/details/69488764
前言
1.本篇博客属于笔记性质,主要整理归纳知识点,方便日后.
2.本篇的内容是基于”Android源码设计模式”.
3.如果有不足的地方,请大家指出,作者会及时更改.
单例模式的介绍
单例模式是应用最广的模式之一,在应用这个模式时,单例对象的类必须保证只有一个实例存在.
许多时候整个系统中只需要拥有一个全局对象,这样有利于我们协调系统整体的行为.
如,在一个应用中,应该只有一个ImageLoader实例,这个ImageLoader中又包含线程池,缓存系统,网络请求等,很消耗资源,因此在这种情况下需要使用单例模式.
单例模式的定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供整个实例,
单例模式的使用场景
确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该只有一个.
例如:访问IO和数据库等资源.
单例模式UML类图
角色介绍:
Client —— 高层客户端(也可以理解成对单例类有需求的对象)
Singleton —— 单例类
单例模式的几种实现方式
懒汉式
public class Singleton { private static Singleton instance; //构造方法私有化 private Singleton() { } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }}
懒汉式特点:
1.懒汉单例模式的优点是单例只有在使用时才会被实例化,在一定程度上解决资源.
2.懒汉单例模式的缺点是第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance()都进行同步,造成不必要的开销.
这种模式一般情况下不建议使用.
Double CheckLock(DCL)实现单例
public class SingletonDCL { //在JDK1.5之后 volatile直接使Java处理器在创建这个对象上,顺序执行,不可乱序执行. private static volatile SingletonDCL instance; private SingletonDCL() { } public static SingletonDCL getInstance() { //第一道锁,主要是为了避免不必要的同步 if (instance == null) { synchronized (SingletonDCL.class) { //第二道锁为了在null的情况下,创建实例. if (instance == null) { instance = new SingletonDCL(); } } } return instance; }}
双重锁检查单例的特点:
1.资源利用率高,第一次执行getInstance时单例对象才会被实例化,实现了懒加载.
2.第一次加载时反应稍慢,并且在JDK1.5之前,不使用volatile关键字,可能在并发环境中出现问题.
DCL模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性.
除非你的代码在并发场景比较复杂或者低于JDK1.6版本下使用,否则,这种方式一般能够满足需求.
静态内部类单例模式
public class SingletonN { private SingletonN() { } /** * 静态内部类 */ private static class SingletonHolder { //内部类静态初始化 private final static SingletonN sIntance = new SingletonN(); } public static SingletonN getInstance() { return SingletonHolder.sIntance; }}
DCL虽然在一定程序上解决了资源消耗、多余的同步、线程安全等问题,但是,它还是在某些情况下出现失效的问题。这个问题被称为双重检查锁定(DCL)失效,在《Java 并发编程实践》一书的最后谈到了这个问题,并指出这种优化还是存在问题的。
静态内部类单例模式的特点:
当第一次加载 Singleton 类时并不会初始化 sIntance,只有在第一次调用 Singleton 的 getInstance 方法时才会导致 sIntance 被初始化。
因为,第一次调用 getInstance 方法会导致虚拟机加载 SingletonHolder 类,这种方式不仅能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐使用的单例模式实现方法。
枚举单例
public enum SingletonEnum { SINGLETON_ENUM; public void doSomething(int i) { //默认枚举实例创建就是线程安全的 }}
枚举单例的特点:
首先,枚举单例的写法比较简单
枚举在 Java 中与普通的类是一样的,不仅能够有字段,还能够有自己的方法。最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。
而且枚举反序列化也不会重新生成新的实例。
使用容器实现单例模式
public class SingletonManager { //储存单例的容器 private static Map<String, Object> objMap = new HashMap<>(); private SingletonManager() { } /** * 添加单例容器 * * @param key 单例容器的 key * @param instance 单例容器的 引用 */ public static void registerService(String key, Object instance) { if (!objMap.containsKey(key)) { objMap.put(key, instance); } } /** * 获取单例 * * @param key 单例容器的 key * @return 容器中的单例对象 */ public static Object getService(String key) { return objMap.get(key); }}
使用容器实现单例的特点:
首先需要在程序的初始,将多种单例类型注入到一个统一的管理类。
然后使用的时候根据 key 获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
个人来看,感觉这种方式更多的还是管理单例的工具类来使用,并且放入存储的对象不能太大,因为这些资源都是在程序开始的使用初始化。
单例模式的总结
不管哪种形式实现单例模式,它们的核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,在这个获取的过程中必须保证线程安全、防止反序列化导致重新生成实例对象等问题。
而选择哪种实现方式取决于项目本身,如是否是复杂的并发环境、JDK版本是否过低、单例对象的资源消耗等。
DCL失效问题
在 JDK1.5之前,DCL实现单例一直是这样的:
public class SingletonDCL { private static SingletonDCL instance; private SingletonDCL() { } public static SingletonDCL getInstance() { //第一道锁,主要是为了避免不必要的同步 if (instance == null) { synchronized (SingletonDCL.class) { //第二道锁为了在null的情况下,创建实例. if (instance == null) { instance = new SingletonDCL(); } } } return instance; }}
DCL模式下的单例,亮点都在 getInstance 方法中对 instance 进行了两次判空:
第一层判断主要是为了避免不必要的同步,第二层的判断则是为了在 null 的情况下创建实例。
在不涉及到并发,多线程的情况下,上述 DCL的模式是一种很好的选择,然后在高并发,多线程的情况下,DCL单例就会出现问题。
sInstance = new SingletonDCL 分析
上述代码表述的是生成一个实例的代码,但是其不是一个原子操作。这句代码最终会被编译成多条汇编指令,它大致做了3件事情:
(1)给 SingletonDCL的实例分配内存(2)调用 Singleton()的构造函数,初始化成员字段(3)将 sIntance 对象指向分配的内存空间(此时 sInstance就不是 null 了)
但是,由于 Java 编译器允许处理器乱序执行,以及 JDK1.5之前 JVM中 Cache、寄存器到主内存回写顺序的规定,上面第二条和第三条是无法保证顺序的。
就是执行顺序可能出现两种结果:
(1) -> (2) -> (3) 正常情况
(1) -> (3) -> (2) DCL失效情况
如果是第二种顺序,并且在3执行完毕,2未执行之前被切换到 B 线程上,这时候 sIntance 因为在线程 A 内执行过了第三点,sInstance 已经非空了,所以,线程 B 直接取走 sInstance,再使用时就会出错,这就是 DCL失效问题。
而且这种错误,难以重现和难以追踪。
在 JDK1.5之后,SUN官方已经注意到这种问题,调整了 JVM、具体优化了 volatile 关键字,如果是 JDK1.5之后的版本,在定义 Instance 时加上 volatile 关键字(用来保证 JVM生成该变量的顺序),就不会发生 DCL失效的问题。
- Android应用最广的模式-单例模式
- 应用最广的模式--单例模式
- 应用最广的模式-单例模式
- Android 设计模式の单例模式——应用最广的模式
- 单例模式——应用最广的模式(二)
- 应用最广的模式-单列模式(结合Android源码)
- Android中使用很广的模式-单例模式
- 设计模式之单例模式——应用最广泛的设计模式
- 单例模式的应用
- 单例模式的应用
- 单例模式的应用
- 单例模式的应用
- 单例模式的应用
- 最实用的单例模式
- 最简单的单例模式
- IOS 最简单的单例模式
- 单例模式最完整的讲解
- 【Unity】最普通的单例模式
- spring MVC 传入 json 数组
- shell学习笔记(二):shell 语法
- MySQL元数据库——information_schema
- Response And Request
- 关于C++函数返回局部对象的详细分析
- 应用最广的模式--单例模式
- stm32 FPU 注意事项
- HTML5中标签之间尽量不要加enter或者空格
- 从网站首页看BAT的发展历史(待续....)
- Android消息机制源码分析
- 正向代理和反向代理
- 【机器学习trick】Batch-Normalization的理解和研究
- 2014 蓝桥杯C/C++B组省赛 六角填数
- spring总结笔记之二