这里写目录标题
1.为什么要使用双重检查索引
1.1 单例模式(懒汉式)存在的问题
public class Singleton {
private static Singleton single = null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) { // 1.线程A执行
single = new Singleton(); // 2.线程B执行
}
return single;
}
}
存在的问题
- 第一种情况
1、线程A执行到了代码1的位置
2、线程B执行到了代码2的位置, 但是还未执行, 正准备创建对象
3、线程A看到single还未被实例化, 就会进入这个判断体重, 再次实例化对象
4、两个线程都实例化对象, 无法保证单例模式 - 第二种情况
1、线程A执行到了代码1的位置
2、线程B执行new Singleton()操作时发生了指令重排, 重排后的指令:先分配内存, 然后赋值给single, 然后再进行初始化(赋值和初始化两个指令被重排了)
3、线程B赋值给single, 此时线程A正好判断instance !=null, 就返回了single
4、此时就会读取到尚未初始化完成的single对象
1.2 使用synchronized保证线程安全的延迟初始化对象
public class Singleton {
private static Singleton single = null;
//静态工厂方法
public synchronized static Singleton getInstance() {
if (single == null) { // 1.线程A执行
single = new Singleton(); // 2.线程B执行
}
return single;
}
}
存在的问题:
1.由于对getInstance()方法做了同步处理,synchronized将导致性能开销。
2.如果getInstance()方法被多个线程频繁的调用,将会导致程序执行性能的下降。
3.如果getInstance()方法不会被多个线程频繁的调用,那么这个延迟初始化方案将能提供令人满意的性能。
1.3 为啥要引入双重检查锁定
由于synchronized存在巨大的性能开销。因此,人们想出了一个“聪明”的技巧:双重检查锁定【Double-Checked Locking】。人们想通过双重检查锁定来降低同步的开销。
线程不安全的双重检查锁定
public class Singleton {
private static Singleton single = null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) { // 1处、第一次检查
// 只有当单例对象为空时, 才实例化对象(同步锁)
synchronized(Singleton.class) { // 2处、加锁
if (single == null) { // 3处、第二次检查
single = new Singleton(); // 4处、实例化对象【这里会出问题的】
}
}
}
return single;
}
}
在1处,如果是第一次检查instance不为null,那么就不需要执行下面的加锁和初始化操作。因此,可以大幅降低synchronized带来的性能开销。
在2处,如果多个线程试图在同一时间创建对象时,这里有同步代码块,会通过加锁来保证只有一个线程能创建对象。
在3处,获得锁的线程,会二次检查这个对象是否已经被初始化。
在4处,对象创建好之后,执行getInstance()方法将不需要获取锁,直接返回已创建好的对象。
问题所在:
一切都是那么的美好,但是有一种情况,在线程执行到1处,读取到instance不为null时;在4处的线程正在初始化实例instance,但是instance引用的对象有可能还没有完成初始化,因为发生了指令重排。
4处因为指令重排,引发的1处拿到的实例在使用的时候发生空指针的问题。(拿到的对象是还未完成初始化的对象)
2.解决方式
2.1 基于volatile的解决方案
public class Singleton {
private static volatile Singleton single = null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) { // 1处、第一次检查
// 只有当单例对象为空时, 才实例化对象(同步锁)
synchronized(Singleton.class) { // 2处、加锁
if (single == null) { // 3处、第二次检查
single = new Singleton(); // 4处、实例化对象【这里会出问题的】
}
}
}
return single;
}
}
volatile 可以禁止single = new Singleton();过程中的指令重排,从而实现线程的安全。