Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Happens-before and reordering of volatile

There a multiple code examples which assume that the following instructions (1) and (2) cannot be reordered:

int value;
volatile boolean ready;

// ...

value = 1;     // (1)
ready = true;  // (2)
  • "What Volatile Means In Java"
  • "Details zu volatile-Variablen" (German)
  • Stack Overflow answer

The latter Stack Overflow answer refers to JLS §17.4.5:

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

However I don't understand why this should apply here since the JLS Example 17.4-1 also states:

[...] compilers are allowed to reorder the instructions in either thread, when this does not affect the execution of that thread in isolation.

which is clearly the case here.

All other definitions in the JLS specific to volatile are only with respect to the same volatile variable, but not to other actions:

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


It confuses me where people see the guarantee that usage of volatile (read or write) may not be reordered.

Could you please base you explanation on the JLS or on other sources which are based on the JLS.

like image 340
Marcono1234 Avatar asked Jan 07 '19 20:01

Marcono1234


1 Answers

In isolation, your code doesn't guarantee anything. There's a second thread involved here and we need its code too! There's a reason why the tutorials you linked to show both threads.

If the code of the two threads is this:

int value;
volatile boolean ready;

// Thread - 1
value = 1;     // (1)
ready = true;  // (2)

// Thread - 2
if (ready) {  // (3)
    x = value // (4)
}

Then we have a happens-before relationship between (1) and (2) due to program order:

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

and we have a happens-before relationship between (2) and (3) due to ready being volatile:

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

And we have a happens-before relationship between (3) and (4) due to program order again:

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

So there's a happens-before chain (1) → (2), (2) → (3), (3) → (4)

And since happens-before is a transitive relation (if A happens before B and B happens before C, then A happens before C) that means that (1) happens before (4).

If we flip (3) and (4) so that the second thread reads value before reading ready, then the happens-before chain breaks, and we no longer get any guarantees about the read from value.

Here's a nice tutorial with some more JMM pitfalls, including this one.

Isn't the Java memory model fun?

like image 84
Malt Avatar answered Oct 13 '22 05:10

Malt