java - volatile 读取冲突

标签 java concurrency volatile double-checked-locking

假设我们使用双重检查锁定来实例化一个单例:

public static Instance getInstance() {
    if (this.instance == null) {
        synchronized(Instance.class) {
            if (this.instance == null) {
                this.instance = new Instance();
            }
        }
    }
    return this.instance;
}

如果 instance 变量将是 volatile 并且将删除双重检查锁定,则问题在于程序的语义。

private volatile static Instance instance;

public static Instance getInstance() {
    if (this.instance == null) {
        this.instance = new Instance();
    }
    return this.instance;
}

这个类只会被实例化一次吗?或者,换句话说, volatile 读取是否会以这样一种方式发生冲突,即两个线程将看到引用的 null 值并执行双重实例化?

我知道 volatile 写入和 volatile 读取之间的happens-before关系,并且 volatile 禁止缓存(因此所有读取和写入都将在主内存中执行,而不是在处理器的缓存中执行),但在并发 volatile 读取的情况下尚不清楚。

P.S.:问题不在于单例模式的应用(这只是一个问题很明显的例子),它只是关于是否可以用 volatile 读取替换双重检查锁定 - volatile 写入而不改变程序语义,仅此而已。

最佳答案

如果没有同步,您的代码肯定会出错,因为 2 个线程可能会看到 instance 的值为 null,并且都将执行初始化(考虑在每一行进行上下文切换,看看会发生什么。

除此之外,即使使用同步双重检查锁定 (DCL),过去在 Java 中也被认为是错误的,因为在非同步运行时,第二个线程可能会以不同的顺序经历初始化操作。 您可以通过添加一个局部变量来修复您的代码,并在您想要读取它时将 volatile 加载到其中:

public static Instance getInstance() {
    Instance tmp = instance;
    if (tmp == null) {
        synchronized(Instance.class) {
            Instance tmp = instance;
            if (tmp == null) {
                instance = new Instance();
            }
        }
    }
    return instance;
}

但更安全的解决方案是使用 ClassLoader 作为您的同步机制,并且还允许您在每次访问单例时停止使用慢速 volatile 访问:

public class Instance {

    private static class Lazy {
        private static Instance INSTANCE = new Instance();    
    }

    public static Instance getInstance() {
        return Lazy.INSTANCE;
    }
}

INSTANCE只有在第一个线程进入getInstance()时才会被初始化

关于java - volatile 读取冲突,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52930084/

相关文章:

java - 如何添加带有或条件的 cron 触发器?

java - 谷歌验证码 : How to get user response and validate in the server side?

java - HashMap<String,Value>.remove() 通过在 Key 上使用 String.intern() 同步,这是否有效?或者这是损坏的代码?

java - 受 Quartz DisallowConcurrentExecution 影响的作业会发生什么

java - java中的初始值之后, volatile 映射可以为空吗?

java - 将数组变量的值赋给自身?

java - 包装类如何将数据类型的值包装在其对象周围?

java - 在Java中,有没有办法将一组可变大小的按钮居中(使用Swing)?

scala - Scala 中的阻塞关键字

c++ - 如何使用 memset 将 volatile 数组设置为零?