Java volatile and happens-before

104 Views Asked by At

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?

2

There are 2 best solutions below

1
Burak Serdar On BEST ANSWER

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.

1
gdomo On

"Subsequent" here means some order in which JVM/OS would order volatile variable accesses. You couldn't directly control that order. Marking a field as volatile also doesn't specify that order.

Starting thread1 before thread2 as you do increases probability that thread1 instructions would be ordered before thread2 instructions, but doesn't guarantee that. Both threads runs in parallel with just a tiny difference at start time, so it's a matter of chance, which one op would be actually executed first by computer. Thread1 just have a little more chances.