Java 内存模型:volatiles 和 reads/writes 重新排序

Java Memory Model: volatiles and reads/writes reordering

我无法理解 JMM 与可能的指令重新排序的关系。例如,让我们考虑以下代码片段:

volatile boolean t1HasArrived = false, t2HasArrived = false;

// In thread 1
t1HasArrived = true;
while (!t2HasArrived) 
    ;

// In thread 2
t2HasArrived = true;
while (!t1HasArrived)
   ;

现在,我愿意相信这段代码实现了两个线程的 Barrier 同步。但我不确定。让我怀疑的是 read/writes 重新排序:JMM 中是否有任何东西会阻止编译器或 CPU 重新排列代码的执行路径,如下面的代码片段?如果不是,你如何证明这种重新排序是允许的?

// In thread 1
while (!t2HasArrived)
    ; 
t1HasArrived = true;

// In thread 2
while (!t1HasArrived)
   ;
t2HasArrived = true;

请注意,我并不是要实现无锁 Barrier。这只是我开始考虑指令重新排序后想到的一个例子。我只想了解如何对其应用 JMM 规则。当只有一个 volatile variable/lock 时,推理一段代码相对容易,但是当有多个时,事情就变得复杂了。

JMM 无法对可变变量重新排序。

在您的情况下,您有一个易失性存储,后跟一个易失性加载,并且不能将它们重新排序为加载后跟存储。 Java.

的所有版本都是如此

有关详细信息,请参阅 The JSR-133 Cookbook for Compiler Writers


Lasciate ogni speranza, voi ch’entrate.

来自the JSR-133 specification

§5 定义

Inter-thread Actions An inter-thread action is an action performed by one thread that can be detected or directly influenced by another thread. Inter-thread actions include reads and writes of shared variables and synchronization actions, such as locking or unlocking a monitor, reading or writing a volatile variable, or starting a thread.

Synchronization Actions Synchronization actions include locks, unlocks, reads of and writes to volatile variables, actions that start a thread, and actions that detect that a thread is done.

§7.3 格式正确的执行

We only consider well-formed executions. An execution E = 〈P, A, po→, so→, W, V, sw→, hb→〉 is well formed if the following conditions are true:

  1. Synchronization order is consistent with program order and mutual exclusion. Having synchronization order is consistent with program order implies that the happensbefore order, given by the transitive closure of synchronizes-with edges and program order, is a valid partial order: reflexive, transitive and antisymmetric. Having synchronization order consistent with mutual exclusion means that on each monitor, the lock and unlock actions are correctly nested.

JMM 将重新排序限制定义为程序指令某些排序的传递闭包。如果写入和读取之间存在顺序,则 JVM 需要根据此顺序 return 值。

在您的情况下,同步顺序 用于任何 volatile 读取 观察 volatile 写入 由字段 volatile 暗示。同步顺序要求线程观察在程序顺序中易失性读取之后易失性写入之前提交的所有写入字段。

这意味着无论何时读取易失性字段,您的应用程序的程序顺序都要求根据程序顺序提交易失性写入,从而导致 happens-before 关系写入读取。因此,您建议的重新排序作为优化是无效的,JMM 保证您的原始源代码所暗示的可见性。

如果您想更详细地了解这一点,我最近给了 presentation on the JMM(时间 - 7 小时 27 分钟)。