求一个示例代码,显示 Java 如何不遵循顺序一致性

问题描述

据我所知,JMM 不遵循顺序一致性,我们需要 volatile 关键字来保证可见性。 没有SC的典型例子经常描述为

A = 0;
B = 0;

--thread 1--
A = 1;
B = 1;

--thread 2--
if(B == 1){
  //at here,A is not guarantee equal to 1,A == 0 is also possible
}

这很容易理解。 但是当我想在真正的 Java 代码中重现这个简单的例子时,我失败了。

下面是我希望重现上述概念示例的代码,这里我没有使用 volatile,但是这段代码仍然没有达到 a==0 和 b==1 的情况。

import java.util.LinkedList;

public class test2 {
    static int a = 0;
    static int b = 0;

    static class Multithreading1 extends Thread {
        public void run() {
            try {
                sleep(50);
            } catch (InterruptedException e) {
                e.printstacktrace();
            }
            a = 1;
            b = 1;
        }
    }

    static class Multithreading2 extends Thread {
        public void run() {
            while (b == 0) {
                Thread.onSpinWait();
            }
            if (a == 0) {
                //never reach
                System.out.print(a);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            a = 0;
            b = 0;
            Multithreading1 t1 = new Multithreading1();
            t1.start();
            LinkedList<Multithreading2> t2 = new LinkedList();
            int thread_number = 16;
            for (int j = 0; j < thread_number; j++) {
                t2.add(new Multithreading2());
            }
            for (int j = 0; j < thread_number; j++) {
                t2.get(j).start();
            }
            for (int j = 0; j < thread_number; j++) {
                t2.get(j).join();
            }
            t1.join();
        }
    }
}

我做错了什么吗? 或者我的失败背后有什么隐藏的机制,如果是这样,是否可以构建一个真实的代码示例来表明Java不遵循顺序一致性?

顺便说一下,我的java版本是OpenJDK Runtime Environment Corretto-11.0.8.10.1

解决方法

JMM 是一种弱内存模型,这意味着普通加载和存储以及同步操作(例如写入/读取易失性变量或解锁/锁定锁定)之间存在分离。 JMM 遵循 SC-DRF 模型(无数据竞争程序的顺序一致性)。 SC-DRF 意味着如果程序的所有可能执行都没有数据竞争,则程序的行为就好像它是顺序一致的。

请记住,如果您在 X86 上运行,那么由于 X86 内存模型 (TSO),将不会对 2 个存储进行重新排序。它们可以乱序执行,但它们将始终退出并按程序顺序 (PO) 提交到缓存。这同样适用于 2 个负载;加载也将在 PO 中执行,即使在引擎盖下它们可能会被推测性地乱序执行。但这是不可能检测到的,因为如果推测失败,CPU 将刷新管道并重试。

此外,编译器也可以优化代码,从而不会因为(有意的)数据竞争而发生重新排序。

这是 JCStress 可以提供帮助的地方: https://openjdk.java.net/projects/code-tools/jcstress

,

作为 pveentjer already answered,JCStress 可以提供帮助。

我只想补充一点,JCStress 源代码中的示例之一就是您的情况:BasicJMM_06_Causality.PlainReads

所以只要研究它的工作原理,您就会发现当我们尝试重现 JMM 的一些极端情况时应该解决哪些细微差别。