javaconcurrencyvolatilehappens-before

Java volatile and happens-before


There is what java spec says about it:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.5
"A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field."
But what does "subsequent" mean in terms of concurrency? How can we be sure what is subsequent ans what is previous? Let's write a simple test:

public class VolatileTest {

    static /*volatile*/ int counter = 0;
    static int zeros = 0;
    static int ones = 0;

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

        int i = 0;
        while (i++ < 1_000_000) {
            if (i % 100_000 == 0) System.out.println(i);
            Thread thread1 = new Thread(VolatileTest::op1);
            Thread thread2 = new Thread(VolatileTest::op2);
            thread1.start();
            thread2.start();

            thread1.join();
            thread2.join();

            counter = 0;
        }

        System.out.println("Zeros " + zeros);
        System.out.println("Ones " + ones);
    }


    public static void op1() {
        counter = 1;
    }

    public static void op2() {
        if (counter == 0) ++zeros;
        if (counter == 1) ++ones;
    }
}

The final output will be like

Zeros 2095
Ones 997905

And it is predictable.
But when i uncomment the volatile word in counter variable, i still get some zeros in the answer, which means that read from volatile count variable was before the write, despite java spec claims that

write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

Where am i wrong?


Solution

  • A write operation to volatile variable is immediately visible to all the threads. More importantly, any write operation that happens before the volatile write will also become visible. So in the following code:

    static volatile int counter = 0;
    static int x = 0;
    
    ...
    // A single thread writes to the counter
    x = 1;
    counter = 1;
    

    For all other threads that run:

    if(counter==1) {
      // Here, x = 1 is guaranteed
    }
    

    The value of x is not guaranteed to be 1 if counter is not volatile. The compiler or the CPU may reorder the memory write operations without the volatile.