为什么在这种情况下会发生 jmm 重新排序?

why jmm reorder happened in this scenarios?

最近一直在想JMM;

如本食谱jsr133-cookbook所述,普通存储然后易失性存储无法重新排序;

can reOrder? 2nd operation
1st operation Normal Load Normal Store Volatile load MonitorEnter Volatile store MonitorExit
Normal Load Normal Store No
Volatile load MonitorEnter No No No
Volatile store MonitorExit No No

现在,我在这里模拟了一个代码场景;

    public static void main(String[] args) throws Exception {
        System.out.println(System.currentTimeMillis());
        for (int i = 0; i < 500000 * 8; i++) {
            final ReOrderClient client = new ReOrderClient();
            Thread t1 = new Thread(client::writer);

            Thread t2 = new Thread(client::reader);

            t1.start();
            t2.start();
        }

        System.out.println("the end");
    }

    private static class ReOrderClient {

        private boolean flag = false;
        private volatile int value = 0;

        private void writer() {
            flag = true;
            value = 2;
        }

        private void reader() {
            if (!flag && value == 2) {
                System.out.println("reOrder happened, client.value=" + value);
            }
        }
    }

我的-CPU-信息:

windows-10

Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz
cpu: 4
L1-cahce:   256 KB
L2-cahce:   1.0 MB
L3-cahce:   6.0 MB

实际测试中,代码执行结果:

reOrder happened, client.value=2
// s1:
private void writer() {
    flag = true;
    value = 2;
}

// s2:
private void writer() {
    value = 2;
    flag = true;
}

因为我认为只有thread1重新排序,出现s2场景,然后thread2才有机会打印重新排序结果; 但是正常写入然后易失性写入导致存储存储围栏,为什么会发生重新排序?

在这个问题中,我知道normal-write then volatile-write不会导致x86中的re-order; 所以只是因为编译器导致重新排序;

为什么会发生编译器重排序,请帮帮我;

您正在查看的文档说:

For a compiler writer, the JMM mainly consists of rules disallowing reorderings...

我、你和阅读该文档的 1_000_000 人中的 999_999 不是编译器编写者。那本书 不是 JVMJMM 的参考实现; JLS chapter 17 is.

JVM,只要它不破坏 JLS 就可以在幕后进行这些类型的转换,它不会以任何方式违反 JLS 的规范.您所说的 StoreStore 围栏在 language specification.

中只是一个不存在的概念

绝对最著名的反例是 lock coarsening,请参阅 了解更多详情。

问题出在 reader。

private static class ReOrderClient {

    private boolean flag = false;
    private volatile int value = 0;

    private void writer() {
        flag = true;
        value = 2;
    }

    private void reader() {
        if (!flag && value == 2) {
            System.out.println("reOrder happened, client.value=" + value);
        }
    }
}

如果我们查看 reader,我们可以将其简化为:

  r1=flag  (plain load)
  r2=value (volatile load)
  

没有什么可以阻止将较早的普通负载重新排序为较晚的易变负载。

在 JMM 级别;由于 2 个加载的顺序错误,存在数据竞争,因为在存储和标志加载之间缺少边缘之前发生了数据竞争。

你需要翻转它们,然后你会在 2 个负载之间得到一个 [LoadLoad]。

   private void reader() {
        if (value == 2 && !flag) {
            System.out.println("reOrder happened, client.flag=" + flag);
        }
    }

这给出:

  r1=value (volatile load)
  [LoadLoad]
  r2=flag  (plain load)
 

现在应该不会发生重新排序。在 JMM 级别上,我们在存储和 'flag'

的负载之间引入了一个 happens before edge

@pveentjer 感谢您的帮助!你的回答很棒。这让我思考;