单例模式——Double Check 双重校验锁实现方式为什么要加volatile关键字
单例模式双重校验锁实例:
public class Singleton {
private static volatile Singleton singleton = null; //声明singleton时必须要加volatile关键字
private Singleton() {}
public static Singleton getInstance(){
//第一次校验singleton是否为空
if(singleton==null){ //1
synchronized (Singleton.class){ //2
//第二次校验singleton是否为空
if(singleton==null){ //3
singleton = new Singleton(); //4
}
}
}
return singleton; //5
}
为什么声明singleton时,需要使用volatile关键字?
第一个作用是:
因为 singleton = new Singleton() 这句话可以分为三步:
- 为 singleton 分配内存空间;
- 初始化 singleton;
- 将 singleton 指向分配的内存空间。
但是由于JVM具有指令重排的特性,执行顺序有可能变成 1-3-2。 指令重排在单线程下不会出现问题,但是在多线程下会导致一个线程获得一个未初始化的实例。例如:线程Thread1先执行了1、3,此时singleton已经指向了分配的内存空间,所以不为null,若这时有Thread2调用 getInstance() 在 //1 处发现 singleton 不为空,因此直接跳到 //5 处 return singleton, 但是此时Thread1并未对 singleton 进行初始化,那么返回的singleton是未被完整创建的singleton。而使用 volatile 会禁止JVM指令重排,从而保证在多线程下也能正常执行。
第二个作用是:
volatile关键字保证了变量的可见性,被volatile关键字修饰的变量,(1)在当前线程的工作内存中修改之后会立即更新回主内存;(2)在其他线程从自己的工作内存中读取该变量值之前,会先从主内存中更新最新的值到各自的工作内存。因此当 进入synchronized同步代码块的线程成功创建了实例,并释放了锁之后,等待在 //2 处的的某个线程拿到锁进入同步代码块,并进行第二次if判断时,读取到的singleton已经成功创建实例,就不会再创建第二个实例,而是直接到 //5 返回创建好的singleton。