发生在之前并不意味着两个任意操作的顺序。更准确地说,在发生之前发生的最重要的事情是将写入和读取捆绑在发生之前 - 一致性。值得注意的是,它告诉了读取可以观察到的写入:最后一次写入按发生顺序进行(在发生之前)或任何其他未按发生顺序排列的写入(种族)。请注意,两次连续读取可能会看到从不同的(不规则)写入中获得的不同值,而不会违反该要求。
例如,JLS 17.4.5 说:
应该注意的是,两个操作之间存在“发生之前”关系并不一定意味着它们在实现中必须按该顺序发生。如果重新排序产生与合法执行一致的结果,则不违法。
数据竞赛是令人毛骨悚然的:不规则的读取可以在每次读取时返回令人惊讶的数据,而Java内存模型可以捕获这一点。因此,更准确的答案是,生成 (1, 0) 的执行不违反 Java 内存模型约束(同步顺序一致性、同步顺序 - 程序顺序一致性、发生前一致性、因果关系要求),因此是允许的。
实现方面:在硬件上,两个负载都可以在不同的时间启动和/或到达内存子系统,无论它们的“程序顺序”如何,因为它们是独立的;在编译器中,指令调度也可能忽略独立读取的程序顺序,以“反直觉”顺序向硬件公开负载。
如果要按程序顺序观察读取,则需要更强的属性。JMM 将该属性赋予同步操作(在您的示例中,创建一个变量将创建该属性),这会将操作按与程序顺序一致的总同步顺序绑定。在这种情况下,(1,0)将被禁止。volatile
一个非常特殊的jcstress测试用例的插图(参见完整的源代码的警告):
private final Holder h1 = new Holder();
private final Holder h2 = h1;
private static class Holder {
int a;
int trap;
}
@Actor
public void actor1() {
h1.a = 1;
}
@Actor
public void actor2(IntResult2 r) {
Holder h1 = this.h1;
Holder h2 = this.h2;
h1.trap = 0;
h2.trap = 0;
r.r1 = h1.a;
r.r2 = h2.a;
}
即使在不重新排序加载的 x86 上,也会产生 (1, 0),哎呀:
[OK] o.o.j.t.volatiles.ReadAfterReadTest
(fork: #1, iteration #1, JVM args: [-server])
Observed state Occurrences Expectation Interpretation
[0, 0] 16,736,450 ACCEPTABLE Doing both reads early.
[1, 1] 108,816,262 ACCEPTABLE Doing both reads late.
[0, 1] 3,941 ACCEPTABLE Doing first read early, not surprising.
[1, 0] 84,477 ACCEPTABLE_INTERESTING First read seen racy value early, and the s...
使挥发性会使(1,0)消失。Holder.a