Java 易失性变量影响其他非易失性变量的内存一致性

Java volatile variables affecting memory consistency of other non-volatile variables

场景A

A1。写入可变变量

A2。将所有本地非易失性变量写入主内存

场景 B

B1。从易失性变量中读取

B2。将所有非易失性变量从主内存重新加载到本地内存

(使用 Java 1.8 / 1.5+)

实际规则是"A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "后续"根据同步顺序定义)。" http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4

换句话说,从一个线程到写入 v 的写入对于另一个线程的读取都是可见的,一旦它在该写入之后读取了 v

我不确定 "flushing to main" 是否是理解这一点的必要方式。 Java 内存模型根据 happens-beforesynchronizes-with 进行记录。我建议用这些术语来考虑它。从概念上讲,JVM 可以省略某些 "flushes",如果它们不是 promise 所必需的。

写入易失性变量保证刷新非易失性变量1。但是,它会在对 volatile 的写入和对 volatile 的任何后续读取之间引入 "happens before" 关系(假设没有对其进行干预写入)。您可以按如下方式利用它:

  1. 线程A:写入NV
  2. 线程A:写入V
  3. 线程 B:读取 V
  4. 线程 B:读取 NV

如果操作按该顺序发生,则线程 B 将在步骤 4 中看到 NV 的更新值。但是,如果某些东西(包括 A)在步骤 2 之后写入 NV,则线程 B 将看到什么是不确定的步骤 4.

一般来说,以这种方式使用 volatiles 需要深入而仔细的推理。使用 synchronized.

更容易也更健壮

你的例子不清楚:

  • 如果它的目的是描述 Java 程序员必须做什么,那是错误的/荒谬的。 Java 代码无法刷新变量。

  • 如果它打算成为必须在实现级别(例如在 JIT 编译代码中)发生的规范,那也是错误的。

  • 如果它的目的是描述可能在实现级别发生的事情(例如在 JIT 编译代码中),它是正确的。

我不是在这里学究气。编译器可能会决定它不需要刷新线程 A 中的 all 局部非易失性,并且它很可能只会重新加载线程 B 中需要的那些。它如何决定?那是编译器作者的事!


1 - JLS 不需要硬件特定的操作,例如刷新。相反,它要求编译后的代码满足一些特定的内存可见性保证,并将实现留给编译器编写者。