java—在读取较新的值之后读取过时的值

roejwanj  于 2021-07-08  发布在  Java
关注(0)|答案(4)|浏览(321)

这个问题在这里已经有答案了

读取的重新排序(2个答案)
上个月关门了。
考虑这个例子。我们有:

int var = 0;

螺纹a:

System.out.println(var);
System.out.println(var);

螺纹b:

var = 1;

线程同时运行。以下输出是否可行?

1
0

也就是说,在读取新值之后读取原始值。这个 var 不易挥发。我的直觉是这不可能。

mm9b1k5b

mm9b1k5b1#

加上其他答案:
long 以及 double ,写入可能不是原子的,因此前32位可能在最后32位之前可见,反之亦然。因此可以输出完全不同的值。

w8biq8rn

w8biq8rn2#

我认为这里缺少的是这样一个事实:这些线程运行在实际的物理内核上,我们这里几乎没有可能的变体:
所有线程都在同一个内核上运行,那么问题就归结为这3条指令的执行顺序,在这种情况下1,0是不可能的,我认为println的执行顺序是由于同步产生的内存障碍,所以排除了1,0
a和b在两个不同的核上运行,那么1,0看起来也不可能,只要运行线程a的核读取1,就不可能在之后读取0,就像上面订购的printlns一样。
线程a在这两个println之间被重新调度,因此第二个println在不同的内核上执行,或者与b曾经/将要执行的相同,或者在不同的第三个内核上执行。因此,当两个println在不同的核上执行时,这取决于两个核看到的值,如果var不同步(不清楚var是其中的一个成员),那么这两个核可以看到不同的var值,因此有可能为1,0。
所以这是一个缓存一致性问题。
p、 我不是一个jvmMaven,所以这里可能还有其他事情要做。

yrefmtwq

yrefmtwq3#

var 是读出来的 1 不会变回来的。无论是由于可见性还是重新排序,此输出都不会发生。可能发生的是 0 0 , 0 1 以及 1 1 .
这里要理解的关键是 println 包括同步。看看里面的方法,你应该看到一个 synchronized 在那里。这些块的效果是打印将按顺序进行。尽管写入操作随时都可能发生,但第一次打印不可能看到 var 但是第二个印刷品看到了旧的价值。因此,写入只能发生在两个打印之前、打印之间或打印之后。
除此之外,也不能保证写操作是可见的 var 未标记为 volatile 写入也没有以任何方式同步。

7rtdyuoh

7rtdyuoh4#

您正在使用 System.out.println 在内部有一个 synchronized(this) {...} 那会让事情变得更糟。但即使这样,你的读者线程仍然可以观察到 1, 0 ,即:一个活泼的读物。
到目前为止,我还不是这方面的Maven,但在浏览了阿列克谢·希皮列夫(alexey shipilev)的大量视频/例子/博客之后,我想我至少了解了一些东西。
jls声明:
如果x和y是同一线程的动作,并且x按程序顺序排在y之前,那么hb(x,y)。
因为两人都读到了 varprogram order ,我们可以得出:

(po) 
firstRead(var) ------> secondRead(var)
// po == program order

这句话还说,这建立了一个 happens-before 顺序,所以:

(hb) 
firstRead(var) ------> secondRead(var)
// hb == happens before

但这是“同一条线”。如果我们想对多个线程进行推理,我们需要研究同步顺序。我们需要这个,因为同一段关于 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 在同一章中起作用:
如果对于a中的所有读取r,其中w(r)是r看到的写操作,则a中的一组操作a在一致之前发生,而不是hb(r,w(r))或存在w.v=r.v且hb(w(r),w)和hb(w,r)。
在一组“在发生之前”一致的操作中,每次读取都会看到一个“在排序之前发生”允许它看到的写入
我承认我很模糊地理解了第一句话,这就是亚历克赛帮助我最多的地方,正如他所说:
读取或看到发生在 happens-before 或任何其他文字。
因为没有 synchronizes-with order 在那里,隐含着没有 happens-before order ,允许读取线程通过race读取。从而得到 1 ,比 0 .
一旦你介绍了一个正确的 synchronizes-with order ,例如这里的一个
监视器m上的解锁操作与所有后续锁定操作同步。。。
对易失性变量v的写入与任何线程对v的所有后续读取同步。。。
图形会发生变化(假设您选择
var volatile ):

SW                       PO
write(var) ---------> firstRead(var) -------> secondRead(var)

// SW == there IS "synchronizes-with order" here
// PO == happens-before
``` `PO` (程序顺序)给出 `HB` (发生在之前)通过我在回答中引用的第一句话。以及 `SW` 给予 `HB` 因为:
如果一个动作x与后面的动作y同步,那么我们还有hb(x,y)。
像这样的:

HB HB
write(var) ---------> firstRead(var) -------> secondRead(var)

现在呢 `happens-before order` 表示读取线程将读取“在上一个hb中写入的”值,或者表示读取 `1` 那么 `0` 这是不可能的。
我以jcstress示例为例,引入了一个小的更改(就像 `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` .

相关问题