Search code examples
javamultithreadingvolatilejava-memory-model

Happens-before mechanism in Java


I've got the question about happens-before mechanism in Java. Here is the example:

public class MyThread extends Thread {

        int a = 0;
        volatile int b = 0;

        public void run() {


            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            read();
        }

        public void write(int a, int b) {
            this.a = a;  
            this.b = b;
        }

        public void read() {
            System.out.println(a + " " + b);
        }


    }

This is clear example of happens-before and this is safe enough, as I know. a will get new value properly, because it stands before b's initialization. But does reordering mean that safety is not guaranteed? I'm talking about this:

public void write(int a, int b) {
    this.b = b;
    this.a = a;//might not work? isn't it?
}

UPD Main thread:

public class Main {

    public static void main(String[] args) throws InterruptedException {

        MyThread t = new MyThread();
        t.start();
        t.write(1, 2);


    }
}

Solution

  • Making b volatile and having write set a value to b after it sets a value to a guarantees that:

    1. if b is read before a, then
    2. a's value will not be older than the value it had when b was previously written to.

    Namely, the write to b and the read from b act as a synchronization point, or a fence, which means whatever was written to a before the write to b will be readable after the read from b (if a is written to more than once, the following writes might or might not be visible).

    However, your code is not safe as it is now. In read, you have the following expression:

    a + " " + b
    

    In Java, expressions are evaluated left-to-right. Which means, a will be evaluated before b. Consequently, it is completely possible that the read of a will see an old value of a, while the read of b will see a new value of b. Had you had b read before a, the happen-before relation between b's write and read would have covered the write to a and the read from a. Now, it doesn't.

    As for reordering operations in write - that will clearly eliminate the effect b's ordering guarantees have over operations on a, even if read first reads b and then reads a.

    To visualize things, given only b is volatile:

    The current code does not guarantee ordering of operations on a -

    write a                         read a
    write b --> happens before -->  read b
    

    Reordering write using the given read will not order a as well -

                                    read a
    write b --> happens before -->  read b
    write a
    

    And even if the given read is fixed, a will not be ordered by b -

    write b --> happens before -->  read b
    write a                         read a
    

    The only safe thing to do is keeping the current write, and fixing the read -

    write a
    write b --> happens before -->  read b
                                    read a