Почему volatile не решает проблему гонки данных во время сравнения - PullRequest
2 голосов
/ 07 мая 2020

Я пытаюсь поэкспериментировать с многопоточностью и следующими примерами отсюда: 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-generi c # 33-Ubuntu SMP Ср 29 апр, 14:32:27 UT C 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

Ответы [ 2 ]

4 голосов
/ 07 мая 2020

Сравнение if (x < y) { не atomi c.

  • t2 загружает x для сравнения
  • t2 перестает работать
  • t1 с шагом x и y
  • t1 остановок
  • t2 запусков
  • t2 загружает y для сравнения
  • , поскольку x - старое значение, а y - новое, увеличенное, x < y - true.

Вот пример того, как решить это с помощью 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");
            }
        }
    }
}
1 голос
/ 07 мая 2020

акузьминых уже объяснил почему if (x < y) может быть правдой. Вы также спросили, почему вы никогда не видите того же явления, когда выполняете if (y > x).

Причина в том, что в java выражения всегда оцениваются слева направо, а когда вы делаете y > x, y всегда будет загружаться из памяти первым, поэтому x уже был увеличен до y, и если x будет считываться с последующей итерации, он также будет больше y.

Вы все еще можете видеть, что «DataRace обнаружен» печатается, когда вы делаете y > x, но это может произойти тогда и только тогда, когда 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");
            }
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...