Java 易失性变量影响其他非易失性变量的内存一致性
Java volatile variables affecting memory consistency of other non-volatile variables
场景A
A1。写入可变变量
A2。将所有本地非易失性变量写入主内存
场景 B
B1。从易失性变量中读取
B2。将所有非易失性变量从主内存重新加载到本地内存
- 场景 A 和 B 是涉及 volatile 的正确行为吗
变量?或者场景A是否也包含B2,或者场景B是否也包含
还包括A2?
- 这些场景是原子的吗?会不会有其他事情发生
在A1和A2之间?还是 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-before 和 synchronizes-with 进行记录。我建议用这些术语来考虑它。从概念上讲,JVM 可以省略某些 "flushes",如果它们不是 promise 所必需的。
写入易失性变量不保证刷新非易失性变量1。但是,它会在对 volatile 的写入和对 volatile 的任何后续读取之间引入 "happens before" 关系(假设没有对其进行干预写入)。您可以按如下方式利用它:
- 线程A:写入NV
- 线程A:写入V
- 线程 B:读取 V
- 线程 B:读取 NV
如果操作按该顺序发生,则线程 B 将在步骤 4 中看到 NV 的更新值。但是,如果某些东西(包括 A)在步骤 2 之后写入 NV,则线程 B 将看到什么是不确定的步骤 4.
一般来说,以这种方式使用 volatiles 需要深入而仔细的推理。使用 synchronized
.
更容易也更健壮
你的例子不清楚:
如果它的目的是描述 Java 程序员必须做什么,那是错误的/荒谬的。 Java 代码无法刷新变量。
如果它打算成为必须在实现级别(例如在 JIT 编译代码中)发生的规范,那也是错误的。
如果它的目的是描述可能在实现级别发生的事情(例如在 JIT 编译代码中),它是正确的。
我不是在这里学究气。编译器可能会决定它不需要刷新线程 A 中的 all 局部非易失性,并且它很可能只会重新加载线程 B 中需要的那些。它如何决定?那是编译器作者的事!
1 - JLS 不需要硬件特定的操作,例如刷新。相反,它要求编译后的代码满足一些特定的内存可见性保证,并将实现留给编译器编写者。
场景A
A1。写入可变变量
A2。将所有本地非易失性变量写入主内存
场景 B
B1。从易失性变量中读取
B2。将所有非易失性变量从主内存重新加载到本地内存
- 场景 A 和 B 是涉及 volatile 的正确行为吗 变量?或者场景A是否也包含B2,或者场景B是否也包含 还包括A2?
- 这些场景是原子的吗?会不会有其他事情发生 在A1和A2之间?还是 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-before 和 synchronizes-with 进行记录。我建议用这些术语来考虑它。从概念上讲,JVM 可以省略某些 "flushes",如果它们不是 promise 所必需的。
写入易失性变量不保证刷新非易失性变量1。但是,它会在对 volatile 的写入和对 volatile 的任何后续读取之间引入 "happens before" 关系(假设没有对其进行干预写入)。您可以按如下方式利用它:
- 线程A:写入NV
- 线程A:写入V
- 线程 B:读取 V
- 线程 B:读取 NV
如果操作按该顺序发生,则线程 B 将在步骤 4 中看到 NV 的更新值。但是,如果某些东西(包括 A)在步骤 2 之后写入 NV,则线程 B 将看到什么是不确定的步骤 4.
一般来说,以这种方式使用 volatiles 需要深入而仔细的推理。使用 synchronized
.
你的例子不清楚:
如果它的目的是描述 Java 程序员必须做什么,那是错误的/荒谬的。 Java 代码无法刷新变量。
如果它打算成为必须在实现级别(例如在 JIT 编译代码中)发生的规范,那也是错误的。
如果它的目的是描述可能在实现级别发生的事情(例如在 JIT 编译代码中),它是正确的。
我不是在这里学究气。编译器可能会决定它不需要刷新线程 A 中的 all 局部非易失性,并且它很可能只会重新加载线程 B 中需要的那些。它如何决定?那是编译器作者的事!
1 - JLS 不需要硬件特定的操作,例如刷新。相反,它要求编译后的代码满足一些特定的内存可见性保证,并将实现留给编译器编写者。