【问题标题】:Why volatile does not solve data race during comparison为什么 volatile 在比较过程中不能解决数据竞争
【发布时间】:2020-05-07 01:13:13
【问题描述】:

我正在尝试使用多线程和以下示例:https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#jls-8.3.1.4

我在下面发布了我的代码。 你能帮我理解为什么“if (x < y) {”会发生数据竞争而不是“if (y > x) {”吗?

我正在使用 openjdk-14.0.1

Linux void-MS-7678 5.4.0-29-generic #33-Ubuntu SMP Wed Apr 29 14:32:27 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

代码:

public class Main {
    public static void main(String[] args) {
        DataRace dr = new DataRace();
        Thread t1 = new Thread(()-> {
            for (int i = 0; i < 100_000; i++) {
                dr.increment();
            }
        });

        Thread t2 = new Thread(()-> {
            for (int i = 0; i < 100_000; i++) {
                dr.check();
            }
        });

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

    private static class DataRace {
        private volatile int x = 0, y = 0;

        public void increment() {
            x++;
            y++;
        }

        public void check() {
            // System.out.println("x=" + x + " y="+ y); // - NO ISSUES
            // if (y > x) { - NO ISSUES
            // if (x < y) { - ISSUES
            if (x < y) {
                System.out.println("DataRace detected: x < y");
            }
        }
    }
}

输出:

/home/void/.jdks/openjdk-14.0.1/bin/java -javaagent:/home/void/Development/idea-IC-183.4588.61/lib/idea_rt.jar=46411:/home/void/Development/idea-IC-183.4588.61/bin -Dfile.encoding=UTF-8 -classpath /home/void/Development/multithreading/out/production/classes Main
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y

Process finished with exit code 0

【问题讨论】:

  • volatile 是关于更改的可见性,而不是竞争条件。
  • @akuzminykh 如果我正确理解了这个例子,就不应该有任何竞争条件。我们应该在 volatile 应该解决的 increment() 方法中进行乱序执行,但是我没有看到它按我预期的那样工作。
  • @JacobG。不,它没有帮助。这是更改后的代码:gist.github.com/bff38874/faefc79fbcbc08c30da5edcc1c7caf43
  • @JacobG.,static 没有理由改变任何东西。无论哪种方式,都有两个共享变量。 static 关键字改变了它们在内存中的位置,但它不会改变它们被两个不同的线程共享的事实,或者访问不同步的事实。
  • 您的代码在“”两种情况下都会出现数据竞争。它们的频率将取决于 VM 版本。例如,它在 Amazon JVMv8 上展示了数十次数据竞争,而在 Amazon JVMv11 上最多展示了一次数据竞争 :)

标签: java multithreading volatile


【解决方案1】:

比较if (x &lt; y) { 不是原子的。

  • t2 加载 x 进行比较
  • t2 停止工作
  • t1 递增 xy
  • t1 停止
  • t2 开始
  • t2 加载 y 进行比较
  • 因为x 是旧值,y 是新值,递增,x &lt; ytrue

以下是如何使用synchronized 解决该问题的示例:

class Main {
    public static void main(String[] args) {
        DataRace dr = new DataRace();
        Thread t1 = new Thread(()-> {
            for (int i = 0; i < 100_000; i++) {
                dr.increment();
            }
        });

        Thread t2 = new Thread(()-> {
            for (int i = 0; i < 100_000; i++) {
                dr.check();
            }
        });

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

    private static class DataRace {
        private volatile int x = 0, y = 0;

        public synchronized void increment() {
            x++;
            y++;
        }

        public void check() {
            // System.out.println("x=" + x + " y="+ y); // - NO ISSUES
            // if (y > x) { - NO ISSUES
            // if (x < y) { - ISSUES
            boolean xSmallerY = false;
            synchronized (this) {
                xSmallerY = x < y;
            }
            if (xSmallerY) {
                System.out.println("DataRace detected: x < y");
            }
        }
    }
}

【讨论】:

  • 啊,谢谢“比较 if (x
【解决方案2】:

akuzminykh 已经explained 为什么if (x &lt; y) 可以是真的。您还问为什么您在if (y &gt; x) 时从未看到相同的现象。

原因是在 java 中表达式总是从左到右计算,当你这样做时 y &gt; xy 总是会首先从内存中加载,所以 xy 之前已经增加了,如果x 将从后续迭代中读取,它也将大于 y

当您执行y &gt; x 时,您仍然可以看到“检测到DataRace”被打印,但是当且仅当x 接近Integer.MAX_VALUE 并且它溢出并在y 之后的后续迭代中变为负数时才会发生这种情况。从内存中读取,然后才从内存中读取x

public class Check {

    public static void main(String[] args) {
        DataRace dr = new DataRace();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100_000; i++) {
                dr.increment();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100_000; i++) {
                dr.check();
            }
        });
        t1.start();
        t2.start();
    }

    private static class DataRace {

        private volatile int x,y;

        public void increment() {
            // to make sure the race condition is caused by the ++ and not by the assignment
            synchronized (this) {
                x = Integer.MAX_VALUE;
                y = Integer.MAX_VALUE;
            }
            x++;
            y++;
        }

        public synchronized void check() {
             if (y > x) {
                 System.out.println("DataRace detected: y > x");
            }
        }
    }
}

【讨论】: