人的一生犹如负重致远,不可急躁。 以不自由为常事,则不觉不足。 心生欲望时,应回顾贫困之日。 心怀宽恕,视怒如敌,则能无视长久。 只知胜而不知敗,必害其身。 责人不如责己,不及胜于过之。
分类: Java
2017-03-02 19:37:23
1 单例模式
单例模式是在项目开发中最为常见的设计模式之一,而单例模式在计算机系统中有很多应用,例如线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。
单例的特性:
1、 在任何情况下,单例类永远只有一个实例存在。
2、 单例需要有能力为整个系统提供这一唯一实例。
2 单例模式会存在的问题
单例模式有很多种实现方式,但是在不同环境中可能遇到可能隐藏着一些问题:
多线程并发下如何保证单例模式的线程安全性呢?如何保证序列化后的单例对象在反序列化后任然是单例的呢?
3 饿汉式单例
饿汉式单例是我最推荐的,简单易懂,而且在JVM层实现了线程安全。
3.1 静态成元初始化特性
饿汉式单例是指在方法调用前,实例就已经创建好了。
实现代码:
点击(此处)折叠或打开
测试代码:
点击(此处)折叠或打开
运行结果:
点击(此处)折叠或打开
3.2 static代码块实现单例
静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特性的实现单例设计模式。
实现代码:
点击(此处)折叠或打开
测试代码:
点击(此处)折叠或打开
测试结果:
点击(此处)折叠或打开
3.3 静态内部类
静态内类在多线程并发下的线程安全问题,其实使用其他方式也可以达到同样的效果。
实现代码:
点击(此处)折叠或打开
测试代码:
点击(此处)折叠或打开
测试结果:
点击(此处)折叠或打开
4 懒汉式单例
懒汉式单例是指在方法调用获取实例时才创建实例,因为相对饿汉式显得“不急迫”,所以被叫做“懒汉模式”。
实现代码:
点击(此处)折叠或打开
点击(此处)折叠或打开
点击(此处)折叠或打开
执行结果:
点击(此处)折叠或打开
4.1 线程安全懒汉式单例
要保证线程安全,我们就得需要使用同步锁机制。加同步锁有好多种方式,下面一步步的解决存在线程安全问题的懒汉式单例。
4.1.1 方法声明synchronized关键字
出现非线程安全问题,是由于多个线程可以同时进入getInstance()方法,那么只需要对该方法进行synchronized的锁同步。
实现代码:
点击(此处)折叠或打开
测试代码:
点击(此处)折叠或打开
测试结果:
点击(此处)折叠或打开
注意:从执行结果上来看,问题已经解决了,但是这种实现方式的运行效率会很低。
4.1.2 同步代码块
对某些重要的代码进行单独的同步,而不是全部进行同步,可以极大的提高执行效率。
实现代码:
点击(此处)折叠或打开
测试代码:
点击(此处)折叠或打开
测试结果:
点击(此处)折叠或打开
注意:这里的实现能够保证多线程并发下的线程安全性,但这样的实现和将全部的代码都被锁上效率是一样很低下。
4.1.3 对重要的代码来进行单独的同步(可能非线程安全)
针对某些重要的代码进行单独的同步,而不是全部进行同步,可以极大的提高执行效率。
实现代码:
点击(此处)折叠或打开
测试代码:
点击(此处)折叠或打开
测试结果:
点击(此处)折叠或打开
4.1.4 双检查机制(Double Check Locking)
为了达到线程安全,又能提高代码执行效率,我们这里可以采用DCL的双检查锁机制来完成。
实现代码:
点击(此处)折叠或打开
测试代码:
点击(此处)折叠或打开
测试结果:
点击(此处)折叠或打开
从运行结果来看,该中方法保证了多线程并发下的线程安全性。这里在声明变量时使用了volatile关键字来保证其线程间的可见性;在同步代码块中使用二次检查,以保证其不被重复实例化。集合其二者,这种实现方式既保证了其高效性,也保证了其线程安全性。
双重检查自身会存在比较隐蔽的问题,查了Peter Haggar在DeveloperWorks上的一篇文章,对二次检查的解释非常的详细:“双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。
双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入,这也是这些习语失败的一个主要原因”。
5 序列化与反序列化的单例模式实现
5.1 未实现序列化与反序列化的单例模式实现
静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。
实现代码:
点击(此处)折叠或打开
测试代码:
点击(此处)折叠或打开
测试结果:
点击(此处)折叠或打开
从结果中我们发现,序列号对象的hashCode和反序列化后得到的对象的hashCode值不一样,说明反序列化后返回的对象是重新实例化的,单例被破坏了。
5.2 解决反序列化后返回的对象重新实例化
解决办法就是在反序列化的过程中使用readResolve()方法。
实现代码:
点击(此处)折叠或打开
测试结果:
点击(此处)折叠或打开
从运行结果可知,添加readResolve方法后反序列化后得到的实例和序列化前的是同一个实例,单个实例得到了保证。
6 枚举类型单例模式
和饿汉式一样,简单易懂,而且在JVM层实现了线程安全。
6.1 枚举数据类型实现单例模式
枚举enum和静态代码块的特性相似,在使用枚举时,构造方法会被自动调用,利用这一特性也可以实现单例
实现代码:
点击(此处)折叠或打开
测试代码:
点击(此处)折叠或打开
测试结果:
点击(此处)折叠或打开
运行结果表明单例得到了保证,但是这样写枚举类被完全暴露了,违反了“职责单一原则”。
6.2 升级版枚举数据类型实现单例模式
不暴露枚举类实现细节的封装代码,实现单一职责
实现代码:
点击(此处)折叠或打开
测试代码:
点击(此处)折叠或打开
测试结果:
点击(此处)折叠或打开
7 总结