【问题标题】:Does Java volatile read flush writes, and does volatile write update readsJava volatile read 是否刷新写入,以及 volatile write 是否更新读取
【发布时间】:2016-06-18 21:43:12
【问题描述】:

我理解 read-acquire(不会在之后的读/写操作重新排序)和 write-release(不会在其之前的读/写操作重新排序)。 我的q是:-

  1. 在读取-获取的情况下,它之前的写入是否会被刷新?
  2. 在写入释放的情况下,之前的读取是否会更新?

另外,Java中read-acquire和volatile read一样吗,write release和volatile write一样吗?

为什么这很重要,让我们以 write-release 为例..

y = x; // a read.. let's say x is 1 at this point
System.out.println(y);// 1 printed
//or you can also consider System.out.println(x);
write_release_barrier();
//somewhere here, some thread sets x = 2
ready = true;// this is volatile
System.out.println(y);// or maybe, println(x).. what will be printed?

此时,x 是 2 还是 1? 在这里,考虑准备好波动。 我知道在 volatile 之前的所有商店都将首先可见..然后只有 volatile 才会可见。谢谢。

参考:-http://preshing.com/20120913/acquire-and-release-semantics/

【问题讨论】:

    标签: java multithreading


    【解决方案1】:

    否:不是所有的写入都被刷新,也不是所有的读取都被更新。

    Java 在多线程的“发生前”基础上工作。基本上,如果 A 发生在 B 之前,B 发生在 C 之前,那么 A 发生在 C 之前。所以你的问题相当于 x=2 是否正式发生在读取 x 的某个动作之前。

    Happens-before 边缘基本上是通过 synchronizes-with 关系建立的,这些关系在 JLS 17.4.4 中定义。有几种不同的方法可以做到这一点,但对于 volatile,它基本上相当于先写入 volatile,然后再读取同一个 volatile:

    • 对 volatile 变量 v 的写入(第 8.3.1.4 节)同步任何线程对 v 的所有后续读取(其中“后续”根据同步顺序定义)。

    鉴于此,如果您的线程写入 ready = true,那么单独写入并不意味着 任何事情发生在它之前(就该写入而言)。实际上恰恰相反;写到ready 发生在其他线程上的事情之前,如果他们读到ready

    所以,如果另一个线程(设置x = 2)已经写入准备就绪之后它设置x = 2,并且这个线程(你在上面发布的)那么 阅读ready,然后它会看到x = 2。这是因为写入发生在读取之前,因此读取线程会看到写入线程所做的一切(直到并包括写入)。否则,您将进行数据竞赛,并且基本上所有赌注都没有了。

    一些补充说明:

    • 如果您没有发生前边缘,您可能仍会看到更新;只是你不能保证。所以,不要假设如果你没有读到对ready 的写入,那么你仍然会看到 x=1。您可能会看到 x=1 或 x=2,或者可能是其他一些写入(直到并包括默认值 x=0)
    • 在您的示例中,y 始终为 1,因为您不会在“此处某处”评论之后重新阅读 x。出于这个答案的目的,我假设在ready = true 之前或之后有第二个y=x 行。如果没有,那么 y 的值将与第一个 println 中的值保持一致(假设没有其他线程直接更改它——如果它是一个局部变量则可以保证),因为线程中的操作总是看起来好像它们没有重新排序。

    【讨论】:

      【解决方案2】:

      Java 内存模型没有指定“读取-获取”和“写入-释放”。这些术语/概念来自其他上下文,正如您所引用的文章非常清楚的那样,它们经常(由不同的专家)用来表示不同的事物。

      如果您想了解 volatile 在 Java 中的工作原理,您需要了解 Java 内存模型和 Java 术语......(幸运的是)这是有充分根据且精确指定的1。尝试将 Java 内存模型映射到“读取-获取”和“写入-释放”语义是一个坏主意,因为:

      • “read-acquire”和“write-release”的术语和语义没有明确规定,并且

      • 假设的 JMM ->“读取-获取”/“写入-释放”语义映射只是 JMM 的一种可能实现。其他映射可能存在具有不同但同样有效的语义


      1 - ... 专家已经注意到 JMM 的某些版本中的缺陷。但关键是,已经做出了认真的尝试来提供理论上合理的规范……作为 Java 语言规范的一部分。

      【讨论】:

        【解决方案3】:

        不,读取 volatile 变量不会刷新之前的写入。可见动作将确保前面的动作可见,但读取 volatile 变量对其他线程不可见。

        不,写入 volatile 变量不会清除先前读取值的缓存。它只保证刷新以前的写入。

        在您的示例中,显然y 在最后一行仍然是 1。根据前面的输出,仅对y 进行了一项分配,即 1。也许那是一个错字,你的意思是写println(x),但即使那样,2 的值也不能保证可见。

        【讨论】:

          【解决方案4】:

          对于您的第一个问题,答案是先进先出顺序

          第二个问题:请查看Volatile Vs Static in java

          【讨论】: