如何理解 JSR-133 Happens-Before 太弱 Figure6/7

How to understand JSR-133 Happens-Before is too Weak Figure6/7

JSR-133 中,有两个示例说明程序大概已正确同步。

第一个示例由图 6 给出,其中:

x == y == 0

然后,线程 1 通过以下方式修改状态:

r1 = x; 
if (r1 != 0) 
y = 1; 

并且线程 2 通过以下方式修改状态:

r2 = y;
if (r2 != 0)
x = 1;

作者声明此程序已正确同步,但是执行此程序导致r1 == r2 == 1。后一个值怎么可能?

此外,图7表示初始状态:

x == y == 0

线程 1 修改:

r1 = x; 
y = r1; 

并且线程 2 正在执行:

r2 = y;
x = r2;

这怎么会导致 r1 == r2 == 42

图6

happens-before 模型仅适用于 synchronizes-withhappens-before 边。 JLS 给出了一个 happens-before 一致执行的例子:

r1 = x;  // sees write of x = 1
y = 1;
r2 = y;  // sees write of y = 1
x = 1;

在此示例中,值“凭空出现”:happens-before 一致但打破了因果关系要求(没有顺序一致的执行可以产生这个结果)。因此,这样的处决不是合法的处决。

图 7

r1 === r2 == 42 不是 一个有效的结果。

没有同步,但唯一有效的结果是所有值都为 0,因为没有其他值分配给变量。例如,在 y = r1 中,r1 具有其默认值 (0) 或具有 x 的值 (0) - 在这两种情况下 y 都将为 0。


作为旁注,您可能应该阅读 Chapter 17 of the JLS 而不是原始的 JSR。

图 6 是 JMM 的 out of thing air 限制示例。因为没有字段被标记为 volatile,所以通常允许 JVM 对示例代码应用优化。这可能会导致意想不到的结果。

假设 线程 1 观察到 x = 1 为真。暂时不要担心,怎么会这样。在这种情况下,线程将设置 y = 1。后一种观察将证明 x = 1 的初步观察是循环推理。如果这是真的,x = y = 1 将是一个合法的结果,并且 JVM 可以简单地替换两个线程中的代码来写入每个值。 JMM 禁止这种形式的推理,因此 x = y = 0 是根据 JMM 唯一合法的结果。

图 7 中也有类似的论点。同样,假设 线程 1 观察到 x = 42。在这种情况下,线程将写入 y = 42。基于这一观察,现在可以争辩说之前的 x = 42 是一个合法的观察。然而,最初的假设是 凭空 禁止的,因此 x = y = 0 是唯一合法的结果。

happens-before 关系的定义仅适用于明确同步的程序,不会禁止假设的循环推理。所以就需要凭空限制。这当然是比较学术的,但是JMM是一个学术模型,定义这个限制还是有必要的。

因此,在任何图中都没有可以取值 xy 的比赛条件,即没有比赛条件, x = y = 0 是唯一可能的观察。由此可见,尽管 volatile.

缺失,但两个示例都已正确同步

图7:(图6已经被其他答案解释清楚了。)

您可以在论文"The Java Memory Model @ POPL'05"中找到图7中示例代码的解释r1 == r2 == 42。以下内容来自第 2.2 节:

图 2(在论文中)包含一个需要仔细语义的程序的常见示例。同步不正确;该程序的所有顺序一致执行都表现出线程 12 之间的数据竞争。但是,我们需要为这段代码提供强有力的保证:我们需要 确保42这样的值不会凭空出现在r1r2.

目前没有任何优化可以允许这样的凭空结果。然而,在未来的攻击性系统中,Thread 1 可以推测性地将值 42 写入 y,这将允许 Thread 2 读取 42 for y 并将其写出到 x,这将允许 Thread 1 读取 42 用于 x,并证明其原始推测写入 42 用于 [=18] =].

像这样的自我辩护的写入推测可能会造成严重的安全违规,需要被禁止。例如,如果凭空产生的值是对线程不应该拥有的对象的引用(例如,由于安全保证),这将是一个严重的问题。