懒汉式单例演进到DCL懒汉式 深度全面解析
懒汉式单例可以分为4个演进过程,接下来将阶段展示,同时解析各自的优缺点以及优化方式和原理。
第一阶段:普通的懒汉式单例
public class Singleton {
private static Singleton singleton = null;
/**
* 第一阶段:普通模式的懒汉式单例,需要注意的有两点:
* 1:构造方法要用private修饰,目的:将构造方法保护起来,在其他类中不能调用单例模式的构造方法,
* 以免破坏单例
* 2:调用获取实例方法getInstance()的时候,先判断对象是否已经初始化。
* 至此普通懒汉式单例完成。但是存在线程安全问题
*
* if判断存在线程安全问题,在并发场景下,可能会有多个线程都进入if,导致创建多个实例。
*/
private Singleton(){
}
public static Singleton getInstance(){
//该模式存在问题:if判断存在线程安全问题,在并发场景下,可能会有多个线程都进入if,导致创建多个实例。
if(singleton ==null){
singleton = new Singleton();
}
return singleton;
}
}
第二阶段:加锁懒汉式单例
public class Singleton {
private static Singleton singleton = null;
private static final Object lock = new Object();
/**
* 第二阶段:为了解决第一阶段的线程安全问题,采用加锁懒汉式单例
* 优化方式: 加锁。
* 加锁的目的:为了解决第一阶段if判断存在的线程安全问题,加锁后,可以解决线程安全问题。
* 存在的缺点:加锁是比较耗费性能的,加锁后,每个线程调用getInstance()方法时,都要先加锁,其实只
* 需要在实例化的时候加锁保证线程安全就行了,以后再获取单例的时候不需要重新实例化,会
* 直接return已实例化的对象,但是目前的锁,在对象实例化完毕之后,也会让每个来获取对
* 象的线程都先加锁,影响性能。
* 存在的问题:该模式的懒汉式,虽然保证了线程安全,但是由于锁的粒度过大,影响性能。
*/
private Singleton(){
}
public static Singleton getInstance(){
synchronized(lock){
if(singleton == null){
singleton = new Singleton();
}
}
return singleton;
}
}
第三阶段:普通DCL懒汉式单例
public class Singleton {
private static Singleton singleton = null;
private static final Object lock = new Object();
/**
* 第三阶段:由于第二阶段锁的粒度过大,影响性能,于是对第二阶段进行优化
* 优化的方式:在进入synchronized代码块之前再加一层if(singleton == null)的判断;
* 目的:在外面套一层if判断后,只要对象第一次实例化后,后续进来的线程if判断出对象已经实例化,
* 就不需要加锁了,直接return实例化好的对象。优化了第二阶段所有调用getInstance()的线
* 程都加锁造成的损耗性能问题。
* 存在的问题:由于在synchronized代码块外面加了一层if(singleton == null)的判断,导致将singleton
* 变量暴露在了synchronized()代码块外面,synchronized可以保证原子性、可见性,但是无法
* 阻止指令重排序,synchronized()代码块内部的代码在获得cpu的使用权时,有可能被指令重排
* 序,该指令重排序会带来一个严重的问题:变量singleton在synchronized内部会被先赋值。
* 从字节码指令分析 new Singleton();会有4个字节码指令
* 1、创建一个对象
* 2、复制对象引用
* 3、调用构造方法
* 4、将对象引用赋值给变量
* 由于指令重排序的问题,可能把3和4顺序调换,并发场景下未获取到锁的线程在if(singleton == null)
* 判断到singleton 不是null。但其实第三步字节码指令构造方法还未执行。
*
* 存在的问题:看似完美的模式,但是如果对有序性理解不深,就会入坑。造成问题的原因:指令重排序。
* 当然不是说指令重排序不好。只是在该场景下如果有并发,指令重排序会带来其他线程可
* 能获取到还未实例化对象的问题
*/
private Singleton(){
}
public static Singleton getInstance(){
if(singleton == null){
synchronized(lock){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
第四阶段:终极版DCL懒汉式单例
public class Singleton {
private static volatile Singleton singleton = null;
private static final Object lock = new Object();
/**
* 第四阶段:针对第三阶段双重if,可能会由于指令重排序带来的问题,进行优化
* 优化的方式:private static Singleton singleton; 加 volatile 修饰;
* 目的:volatile可以保证可见性、有序性,加了volatile修饰,将会保证有序性,volatile通过
* 读写屏障保证有序性,singleton被volatile修饰,在singleton赋值的时候添加写屏障,
* 保证写屏障后面的指令不会出现在singleton赋值的前面。
* 由于添加了volatile,保证了有序性,所以至此,DCL懒汉式单例完成。DCL懒汉式单例既保证
* 了线程安全,又优化了加锁带来的性能损耗问题,同时有了volatile的修饰,也保证了不会由于发
* 生指令重排,造成线程并发时获取到还未实例化的对象问题。
*
* 至此DCL懒汉式单例终极版完成。
*/
private Singleton(){
}
public static Singleton getInstance(){
if(singleton == null){
synchronized(lock){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
小结
一个单例模式其实用到了Java并发方面的知识,想写好一个懒汉式单例并且理解原理还是不太容易的。代码就是一个不断优化的过程,然后优化的方式在解决原有问题的同时也可能带来新的问题,接着继续优化,循序渐进的让代码趋于完美