java - volatile 变量读取行为

标签 java concurrency lazy-loading volatile vavr

我在阅读Vavr的Lazy时遇到过如下代码源代码:

private transient volatile Supplier<? extends T> supplier;
private T value; // will behave as a volatile in reality, because a supplier volatile read will update all fields (see https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile)
public T get() {
    return (supplier == null) ? value : computeValue();
}

private synchronized T computeValue() {
    final Supplier<? extends T> s = supplier;
    if (s != null) {
        value = s.get();
        supplier = null;
    }
    return value;
}
这是一个著名的模式,称为“双重检查锁定”,但对我来说它看起来很糟糕。假设我们将此代码嵌入到多线程环境中。如果get()方法由第一个线程调用,供应商构造一个新对象,(对我而言)由于以下代码的重新排序,另一个线程有可能看到半构造的对象:
private synchronized T computeValue() {
    final Supplier<? extends T> s = supplier;
    if (s != null) {
        // value = s.get(); suppose supplier = () -> new AnyClass(x, y , z)
        temp = calloc(sizeof(SomeClass));
        temp.<init>(x, y, z);
        value = temp; //this can be reordered with line above
        supplier = null;
    }
    return value;
}
不幸的是,value旁边有一条评论 field :
private transient volatile Supplier<? extends T> supplier;
private T value; // will behave as a volatile in reality, because a supplier volatile read will update all fields (see https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile)
据我所知, volatile 读取将“刷新” volatile 读取后读取的变量值。换句话说 - 它耗尽了缓存的失效队列(如果我们谈论 MESI 一致性协议(protocol))。此外,它可以防止在 volatile 读取之后发生的加载/读取在它之前被重新排序。尽管如此,它并不保证对象创建中的指令(调用供应商 get() 方法)不会被重新排序。
我的问题是 - 为什么这段代码被认为是线程安全的?
显然,我在评论中的来源中没有发现任何有趣的东西。

最佳答案

当谈到 Java 的内存模型时,不要谈论缓存。重要的是正式的先发生于关系。
请注意 computeValue()已声明 synchronized ,所以对于执行该方法的线程来说,方法内的重新排序是无关紧要的,因为它们只能在之前执行该方法的任何线程已经退出该方法时才能进入该方法,并且前一个方法退出之间存在happens-before关系线程和进入方法的下一个线程。
真正有趣的方法是

public T get() {
    return (supplier == null) ? value : computeValue();
}
不使用 synchronized但依赖于 volatile阅读 supplier .这显然是假设 supplier 的初始状态是非 null ,例如在构造函数和周围代码中分配确保get在此分配发生之前,方法无法执行。
然后,当 supplier读作 null ,只能是写的结果,第一个线程执行computeValue()已完成,这在线程在分配 null 之前所做的写入之间建立了一个发生在之前的关系。至 supplier以及该线程在阅读后所做的阅读 null来自 supplier .所以它会感知到 value 引用的对象的完全初始化状态。 .
所以你是对的,值的构造函数中发生的事情可以通过 value 的赋值重新排序。引用,但它们不能在随后写入 supplier 时重新排序,其中 get方法是靠。

关于java - volatile 变量读取行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69034955/

相关文章:

java - 如何在 JSP 中公开 bean?

java - JSF - 动态添加的组件的绑定(bind)值

java - Java 中的麦克风级别

ios - 为什么 NSOperation 在上一个操作完成之前就开始了?

ios - 在 UIScrollView iOS7 中延迟加载图像时内存增加

java - 将 bytearray 转换为 Objective-C 数据结构

mysql - ROR 中的数据库安全并发

java - 在 Swing 的 JTextArea 上使用 setText 时出现死锁

reactjs - React 解决方法中的命名延迟导入

entity-framework - 我应该关闭 Entity Framework 中的延迟加载吗?