java 单例模式中的双重检测为什么要加 volatile 关键字?

Java 实现单例模式有方法有双重检测锁,代码如下:

public class Singleton {

private static volatile Singleton singleton = null;

private Singleton(){}

public static Singleton getSingleton(){

if(singleton == null){

synchronized (Singleton.class){

if(singleton == null){

singleton = new Singleton();

}

}

}

return singleton;

}

}

我理解的 synchronized 关键字实现了可见性、原子性和有序性,临界区中的代码可以重排序,但是不能重排序到临界区外面,synchronized 实现的可见性是临界区中代码执行结束之后,里面的共享变量会刷新到主内存中,那么如果 new Singleton() 方法被拆成了三个操作,并且经过重排序之后的顺序是这样的话:

  1. 分配内存
  2. 将实例引用赋值给 singleton 变量
  3. 实例初始化

不管这三个操作怎么重排序,另外一个线程看到的结果都是这三个操作执行完成后的结果(因为 synchronized 的原子性),那不就相当于另外一个线程访问到的 singleton 如果不为 null 的话就肯定实例化了吗?为什么还要多此一举加个 volatile 关键字禁止重排序呢?


回答:

我大概了解你的疑惑点,上面的评论其实已经可以解决你的疑惑了。
另外一个线程访问到的 singleton 如果不为 null 的话就肯定实例化了吗
问题的关键在于,Sychronized加锁的位置,它没有对getSingleton()整个方法解锁,而是判断singleton为null后才会去抢锁,所以多个线程可以同时进入getSingleton方法。
由于synchronized 的有序性是持有相同锁的两个同步块只能串行的进入,即被加锁的内容要按照顺序被多个线程执行,但是其内部的同步代码还是会发生重排序,使块与块之间有序可见。
那么如果不加上volatile防止指令的重排序,new Singleton() 方法被拆成了三个操作,并且经过重排序之后的顺序是这样的话:

  1. 分配内存
  2. 将实例引用赋值给 singleton 变量
  3. 实例初始化

其中线程A假设在sychronized块中将内存地址赋值给了对象,其他线程此时调用getSingleton(),发现singleton此时不为空了,那么直接返回singleton,但是此时singleton还未完成初始化,那么问题就出现了。


回答:

右上角单词:Singleton

以上是 java 单例模式中的双重检测为什么要加 volatile 关键字? 的全部内容, 来源链接: utcz.com/p/945412.html

回到顶部