Deep understanding of volatile in Java

Viewed 269

Does Java allows output 1, 0? I've tested it very intensively and I cannot get that output. I get only 1, 1 or 0, 0 or 0, 1.

public class Main {
    private int x;
    private volatile int g;

    // Executed by thread #1
    public void actor1(){
       x = 1;
       g = 1;
    }

    // Executed by thread #2
    public void actor2(){
       put_on_screen_without_sync(g);
       put_on_screen_without_sync(x);
    }
}

Why?

On my eye it is possible to get 1, 0. My reasoning. g is volatile so it causes that memory order will be ensured. So, it looks like:

actor1:

(1) store(x, 1)
(2) store(g, 1)
(3) memory_barrier // on x86

and, I see the following situation: reorder store(g, 1) before store(x,1) (memory_barrier is after (2)). Now, run thread #2. So, g = 1, x = 0. Now, we have expected output. What is incorrect in my reasoning?

4 Answers

You will never see 1, 0, but properly explaining this is not going to be easy, spec wise. First let's get some obvious things out of the door. The specification says:

If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).

This means that on the writing thread side, hb(x, g) and on the reading side hb(g, x). But this is only so, if you would have to reason about each thread individually, as the chapter about Program order says::

Among all the inter-thread actions performed by each thread t...

So if you imagine running each thread at a time, then happens-before would be correct for each of them, individually. But you don't. Your actors (I am sure you use jcstress there) run concurrently. So relying on "program order" for reasoning is not enough (neither it is correct).

You need to somehow synchronize these two actions now - the reading and the writing. And here is how the specification says it can be done:

A write to a volatile variable synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).

And later says:

If an action x synchronizes-with a following action y, then we also have hb(x, y).

If you put all of these together now:

          (hb)              (hb)             (hb)
write(x) ------> write(g) -------> read(g) -------> read(x)

This is also called to "transitively" close program order and synchronizes-with order. Since there is hb on every step, seeing 1, 0 (a racy read), is impossible according to the spec.

Related