复制来自 http://blog.csdn.net/cselmu9/article/details/51366946java
在全部的设计模式中,单例模式是咱们在项目开发中最为常见的设计模式之一,而单例模式有不少种实现方式,你是否都了解呢?高并发下如何保证单例模式的线程安全性呢?如何保证序列化后的单例对象在反序列化后任然是单例的呢?这些问题在看了本文以后都会一一的告诉你答案,赶快来阅读吧!mysql
在文章开始以前咱们仍是有必要介绍一下什么是单例模式。单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。sql
从概念中体现出了单例的一些特色:数据库
(1)、在任何状况下,单例类永远只有一个实例存在编程
(2)、单例须要有能力为整个系统提供这一惟一实例 设计模式
为了便于读者更好的理解这些概念,下面给出这么一段内容叙述:缓存
在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具备资源管理器的功能。每台计算机能够有若干个打印机,但只能有一个Printer Spooler,以免两个打印做业同时输出到打印机中。每台计算机能够有若干通讯端口,系统应当集中管理这些通讯端口,以免一个通讯端口同时被两个请求同时调用。总之,选择单例模式就是为了不不一致状态,避免政出多头。
正是因为这个特色,单例对象一般做为程序中的存放配置信息的载体,由于它能保证其余对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或文件中,这些配置数据由某个单例对象统一读取,服务进程中的其余对象若是要获取这些配置信息,只需访问该单例对象便可。这种方式极大地简化了在复杂环境 下,尤为是多线程环境下的配置管理,可是随着应用场景的不一样,也可能带来一些同步问题。
安全
舒适提示:本文叙述中涉及到的相关源码能够在这里进行下载源码,读者可免积分下载。服务器
饿汉式单例是指在方法调用前,实例就已经建立好了。下面是实现代码:多线程
以上是单例的饿汉式实现,咱们来看看饿汉式在多线程下的执行状况,给出一段多线程的执行代码:
以上代码运行结果:
从运行结果能够看出实例变量额hashCode值一致,这说明对象是同一个,饿汉式单例实现了。
懒汉式单例是指在方法调用获取实例时才建立实例,由于相对饿汉式显得“不急迫”,因此被叫作“懒汉模式”。下面是实现代码:
这里实现了懒汉式的单例,可是熟悉多线程并发编程的朋友应该能够看出,在多线程并发下这样的实现是没法保证明例实例惟一的,甚至能够说这样的失效是彻底错误的,下面咱们就来看一下多线程并发下的执行状况,这里为了看到效果,咱们对上面的代码作一小点修改:
这里假设在建立实例前有一些准备性的耗时工做要处理,多线程调用:
执行结果以下:
从这里执行结果能够看出,单例的线程安全性并无获得保证,那要怎么解决呢?
要保证线程安全,咱们就得须要使用同步锁机制,下面就来看看咱们如何一步步的解决 存在线程安全问题的懒汉式单例(错误的单例)。
出现非线程安全问题,是因为多个线程能够同时进入getInstance()方法,那么只须要对该方法进行synchronized的锁同步便可:
此时任然使用前面验证多线程下执行状况的MyThread类来进行验证,将其放入到org.mlinge.s03包下运行,执行结果以下:
从执行结果上来看,问题已经解决了,可是这种实现方式的运行效率会很低。同步方法效率低,那咱们考虑使用同步代码块来实现:
这里的实现可以保证多线程并发下的线程安全性,可是这样的实现将所有的代码都被锁上了,一样的效率很低下。
针对某些重要的代码进行单独的同步,而不是所有进行同步,能够极大的提升执行效率,咱们来看一下:
此时一样使用前面验证多线程下执行状况的MyThread类来进行验证,将其放入到org.mlinge.s04包下运行,执行结果以下:
从运行结果来看,这样的方法进行代码块同步,代码的运行效率是可以获得提高,可是却没能保住线程的安全性。看来还得进一步考虑如何解决此问题。
为了达到线程安全,又能提升代码执行效率,咱们这里能够采用DCL的双检查锁机制来完成,代码实现以下:
将前面验证多线程下执行状况的MyThread类放入到org.mlinge.s05包下运行,执行结果以下:
从运行结果来看,该中方法保证了多线程并发下的线程安全性。
这里在声明变量时使用了volatile关键字来保证其线程间的可见性;在同步代码块中使用二次检查,以保证其不被重复实例化。集合其两者,这种实现方式既保证了其高效性,也保证了其线程安全性。
DCL解决了多线程并发下的线程安全问题,其实使用其余方式也能够达到一样的效果,代码实现以下:
以上代码就是使用静态内置类实现了单例模式,这里将前面验证多线程下执行状况的MyThread类放入到org.mlinge.s06包下运行,执行结果以下:
从运行结果来看,静态内部类实现的单例在多线程并发下单个实例获得了保证。
静态内部类虽然保证了单例在多线程并发下的线程安全性,可是在遇到序列化对象时,默认的方式运行获得的结果就是多例的。
代码实现以下:
序列化与反序列化测试代码:
运行以上代码,获得的结果以下:
从结果中咱们发现,序列号对象的hashCode和反序列化后获得的对象的hashCode值不同,说明反序列化后返回的对象是从新实例化的,单例被破坏了。那怎么来解决这一问题呢?
解决办法就是在反序列化的过程当中使用readResolve()方法,单例实现的代码以下:
再次运行上面的测试代码,获得的结果以下:
从运行结果可知,添加readResolve方法后反序列化后获得的实例和序列化前的是同一个实例,单个实例获得了保证。
静态代码块中的代码在使用类的时候就已经执行了,因此能够应用静态代码块的这个特性的实现单例设计模式。
测试代码以下:
运行结果以下:
从运行结果看,单例的线程安全性获得了保证。
枚举enum和静态代码块的特性类似,在使用枚举时,构造方法会被自动调用,利用这一特性也能够实现单例:
测试代码以下:
执行后获得的结果:
运行结果代表单例获得了保证,可是这样写枚举类被彻底暴露了,听说违反了“职责单一原则”,那咱们来看看怎么进行改造呢。
不暴露枚举类实现细节的封装代码以下:
验证单例实现的代码以下:
验证结果:
验证结果代表,完善后的单例实现更为合理。
以上就是本文要介绍的全部单例模式的实现,相信认真阅读的读者都已经明白文章开头所引入的那几个问题了,祝你们读得开心:-D!
备注:本文的编写思路和实例源码参照《Java多线程编程核心技术》-(高洪岩)一书中第六章的学习案例撰写。