您正在使用System.out.println,它在内部执行synchronized(this) {...},这会使事情变得更糟。但即便如此,您的读者线程仍然可以观察到1, 0,即:一个活泼的阅读。
到目前为止,我还不是这方面的专家,但是在浏览了 Alexey Shipilev 的大量视频/示例/博客之后,我想我至少了解了一些东西。
JLS states那个:
如果 x 和 y 是同一线程的操作,并且 x 在程序顺序中位于 y 之前,则为 hb(x, y)。
由于var的两个读取都在program order中,我们可以绘制:
(po)
firstRead(var) ------> secondRead(var)
// po == program order
那句话还说,这建立了happens-before 订单,所以:
(hb)
firstRead(var) ------> secondRead(var)
// hb == happens before
但那是在“同一个线程”中。如果我们想推理多线程,我们需要查看synchronization order。我们需要它,因为关于 happens-before order 的同一段说:
如果动作 x 与后续动作 y 同步,那么我们也有 hb(x, y)。
因此,如果我们在program order 和synchronizes-with order 之间构建这个动作链,我们就可以推断结果。让我们将其应用于您的代码:
(NO SW) (hb)
write(var) ---------> firstRead(var) -------> secondRead(var)
// NO SW == there is "no synchronizes-with order" here
// hb == happens-before
这就是happens-before consistency 在same chapter 中发挥作用的地方:
如果对于 A 中的所有读取 r,一组动作 A 在发生之前是一致的,其中 W(r) 是 r 看到的写入动作,但不是 hb(r, W(r))或者在 A 中存在写 w 使得 wv = rv 和 hb(W(r), w) 和 hb(w, r)。
在happens-before一致的一组动作中,每次读取都会看到一次写入,而happens-before排序则允许它看到
我承认我对第一句话的理解非常模糊,正如他所说,这是 Alexey 对我帮助最大的地方:
读取查看happens-before 中发生的最后一次写入或任何其他写入。
因为那里没有synchronizes-with order,并且隐含地没有happens-before order,所以允许读取线程通过竞赛读取。
从而得到1,而不是0。
只要你介绍一个正确的synchronizes-with order,for example one from here
监视器 m 上的解锁操作与...上的所有后续锁定操作同步
对 volatile 变量 v 的写入与任何线程对 v 的所有后续读取同步...
图表发生变化(假设您选择制作varvolatile):
SW PO
write(var) ---------> firstRead(var) -------> secondRead(var)
// SW == there IS "synchronizes-with order" here
// PO == happens-before
PO(程序顺序)通过我在 JLS 的这个答案中引用的第一句话给出了 HB(发生在之前)。而SW 给出HB 因为:
如果动作 x 与后续动作 y 同步,那么我们也有 hb(x, y)。
这样:
HB HB
write(var) ---------> firstRead(var) -------> secondRead(var)
现在happens-before order表示读取线程将读取“写入最后一个HB”的值,或者这意味着读取1然后读取0是不可能的。
我以jcstress samples 为例,做了一个小改动(就像你的System.out.println 所做的那样):
@JCStressTest
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE, desc = "Doing both reads early.")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "Doing both reads late.")
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE, desc = "Doing first read early, not surprising.")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "First read seen racy value early, and the second one did not.")
@State
public class SO64983578 {
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(II_Result r) {
Holder h1 = this.h1;
Holder h2 = this.h2;
h1.trap = 0;
h2.trap = 0;
synchronized (this) {
r.r1 = h1.a;
}
synchronized (this) {
r.r2 = h2.a;
}
}
}
注意synchronized(this){....} 不是初始示例的一部分。即使有同步,我仍然可以看到 1, 0 结果。这只是为了证明即使使用synchronized(内部来自System.out.println),您仍然可以获得1 而不是0。