首先先看一个使用双重检查的单例模式:
public class DoubleCheckedLock{
private static DoubleCheckedLock instance;
public static DoubleCheckedLock getInstance(){
if(instance == null){
synchronized(DoubleCheckdLock.class)
if(instance == null){
instance = new DoubleCheckedLock();
}
}
return instance;
}
}
双检锁机制的出现确实是解决了多线程并行中不会出现重复new对象,而且也实现了懒加载,但很遗憾的是 instance = new DoubleCheckedLock()在编译器下实现是有过程的
过程1. 给新的实体instance分配内存
过程2. 调用DoubleCheckedLock的构造函数给instance初始化
过程3. 将incetance指向分配的内存空间
在这三个过程之间,由于JVM的“优化”机制,如果出现多线程并发访问的情况,就会出现A线程在执行过程2的时候,已经分配好了内存,此时想要进入过程3,不巧的是,线程B进入了过程2,此时instance并不为null(内存已经被分配),导致线程B此时不执行创建对象的语句,改为直接返回一个未初始化的DoubleCheckedLock对象!
可以使用volatile关键字来定义该变量的语义,使得每次修改instance后,线程工作内存强制刷新到主存中,禁止了JVM的指令重排序,防止了该问题
使用volatile另一个方面则使instance对于其他线程可见,这里被锁住的是DoubleCheckedLock.class而非instance,如果不使用volatile,则有可能引发当前变量更改后只存在于自身线程的工作内存中(即未同步到主存)而引发的多个线程创建多个实例的后果,导致单例模式失去意义。