发生以前并不意味着两个任意操作的顺序。更确切地说,发生的最重要的事情 - 之前所做的是绑定写入和读取在发生 - 在一致性之前发生。值得注意的是,它告诉读者可以观察哪些写操作:最后一次写操作发生在订单之前,或者其他任何未订购的写操作发生之前(竞争)。请注意,连续两次读取可能会看到从不同(写入)写入获得的不同值,而不会违反该要求。
E.g. JLS 17.4.5说:
应当注意的是,之前发生关系 两者行动的存在并不一定意味着他们必须采取 发生在执行的顺序。如果重新排序产生与合法执行一致的结果 ,则它不是非法的。
数据竞赛令人毛骨悚然:racy读取可以在每次读取时返回令人惊讶的数据,而Java存储器模型可捕获该数据。因此,更准确的答案是产生(1,0)的执行不违反Java内存模型约束(同步顺序一致性,同步顺序 - 程序顺序一致性,在一致性和因果关系要求之前发生),因此允许。实施方式:在硬件上,两个负载可以在不同时间启动和/或到达存储器子系统,而不管它们的“程序顺序”如何,因为它们是独立的;在编译器中,指令调度也可能忽略独立读取的程序顺序,从而以“逆直觉”顺序将负载暴露给硬件。
如果你想看到要在程序顺序中观察,你需要一个更强的属性。 JMM将该属性设置为同步操作(在您的示例中,使变量volatile
可以做到这一点),该操作将总计同步顺序中的操作绑定为一致的与程序顺序。在这种情况下,(1,0)将被禁止。
插图上的very special jcstress testcase(见注意事项的完整源):
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...
制作Holder.a
挥发性会使(1,0)消失。
是的,这是正确的。但是,编译器不是“重新排序”线程。编译器只是编译。 – Elyasin
我怀疑'1,0'是可能的。这看起来非常非常错误。 – luk2302
我会很惊讶地看到1,0。你真的看过这个输出吗? – bhspencer