第 6 章 单例模式与多线程
本章主要内容
如何使单例模式遇到多线程是安全的、正确的。
6.1 立即加载 / “饿汉模式”
什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接 new 实例化。而立即加载也称为“饿汉模式”。
6.2 延迟加载 / “懒汉模式”
什么是延迟加载?延迟加载就是在调用 get() 方法时实例才被创建,常见的实现办法就是在 get() 方法中进行 new 实例化。延迟加载也称为“懒汉模式”。
1. 延迟加载 / “懒汉模式” 解析
延迟加载 / “懒汉模式” 是在调用方法时实例才被创建。
这种模式如果是在多线程的环境中,就会出现取出多个实例的情况,与单例模式的初衷是相背离的。
2. 延迟加载 / “懒汉模式” 的缺点
“延迟加载”在多线程的环境中,根本不能实现保持单例的状态。
3. 延迟加载 / “懒汉模式”的解决方案
(1)声明 synchronized 关键字
既然多个线程可以同时进入 getInstance() 方法,那么只需要对 getInstance() 方法声明 synchronized 关键字即可。
为 getInstance() 方法加入同步 synchronized 关键字得到相同实例的对象,但此种方法的运行效率非常低下,是同步运行的,下一个线程想要取得对象,则必须等上一个线程释放锁之后,才可以继续执行。
(2)尝试同步代码块
同步方法是对方法的整体进行持锁,这对运行效率来讲是不利的。
同步 synchronized 语句块得到相同实例的对象,但此种方法的运行效率也是非常低的,和 synchronized 同步方法一样是同步运行的。
(3)针对某些重要的代码进行单独的同步
同步代码块可以针对某些重要的代码进行单独的同步,而其他的代码则不需要同步。这样在运行时,效率完全可以得到大幅提升。
此方法使用同步 synchronized 语句块,只对实例化对象的关键代码进行同步,从语句的结构上来讲,运行的效率的确得到了提升。但如果是遇到多线程的情况下还是无法解决得到同一个实例对象的结果。
(4)使用 DCL 双检查锁机制
使用 DCL 双检查锁机制来实现多线程环境中的延迟加载单例设计模式。
DCL 双检查模式就是一方面使用 synchronized 语句款,只对实例化对象的关键代码进行同步,另一方面对单例对象使用 volatile 关键字。
使用双重检查锁功能,成功地解决了“懒汉模式”遇到多线程的问题。DCL 也是大多数多线程结合单例模式使用的解决方案。
6.3 使用静态内置类实现单例模式
DCL 可以解决多线程单例模式的非线程安全问题。当然,使用其他的办法也能达到同样的效果。
public class MyObject { //内部类方式 public static class MyObjectHandler { private static MyObject myobject = new MyObject(); } private MyObject() { } public static MyObjectgetInstance() { return MyObjectHandler.myObject; }}
6.4 序列化与反序列化的单例模式实现
静态内置类可以达到线程安全问题,但如果遇到序列化对象时,使用默认的方式运行得到的结果还是多例的(单例类 implements Serializable)。
文件读和写获取的对象实例不同,解决办法就是在反序列中使用 readResolve() 方法。
6.5 使用 static 代码块实现单例模式
静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特性来实现单例设计模式。
static { instance = new MyObject();}
6.6 使用 enum 枚举数据类型实现单例模式
枚举 enum 和静态代码块的特性相似,在使用枚举类时,构造方法会被自动调用,也可以应用其这个特性实现单例设计模式。
public enum MyObject{... private MyObject(){ //单例初始化 }}
6.7 完善使用 enum 枚举实现单例模式
前面一节将枚举类进行暴露,违反了“职责单一原则”。
public class MyObject{ public enum MyEnumSingletion{ ... private MyEnumSingletion(){ //单例初始化 } }}
6.8 本章总结
本章使用若干案例来阐述单例模式与多线程结合时遇到的情况与解决方法。