Search code examples
javamultithreadingvolatile

Why volatile does not solve data race during comparison


I'm trying to experiment with multithreading and following examples from here: https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#jls-8.3.1.4

I've posted my code below. Could you please help me to understand why data race happen for "if (x < y) {" and not for "if (y > x) {" ?

I'm using 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

Code:

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");
            }
        }
    }
}

Output:

/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

Solution

  • The comparison if (x < y) { is not atomic.

    • t2 loads x for the comparison
    • t2 stops working
    • t1 increments x and y
    • t1 stops
    • t2 starts
    • t2 loads y for the comparison
    • as x is the old value and y is new, incremented, x < y is true.

    Here is an example of how to solve that with 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");
                }
            }
        }
    }