6

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)

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.

Malt
  • 28,965
  • 9
  • 65
  • 105
Marcono1234
  • 5,856
  • 1
  • 25
  • 43
  • 2
    The example you give is basically taken directly from *Java Concurrency in Practice* by Brian Goetz, so we know that the reordering can't happen, the int `value` **MUST** be visible after a read of `ready` == true. But I agree that the spec is a little vague here. Personally I take Goetz's book to just as normative as the spec, if not more so, and just proceed from there. – markspace Jan 07 '19 at 20:46
  • My guess would be that "execution of a thread in isolation" includes considering that a volatile was written, and therefor any optimizer must know there must be *happens-before* semantics associated with the volatile, so the reordering is prevented. Like you, I'd like a definitive source on that, but something like that must be happening or the whole thing falls apart. – markspace Jan 07 '19 at 20:49
  • @markspace the specification still does not forbid reordering, regardless of whether the variable has been declared `volatile` or not. All that has been specified, is, which perceivable behavior an implementation must guaranty, so if it is capable of doing so while still reordering the writes, everything is fine. In practice, this is often implemented by restricting reordering optimizations, but to name one conforming alternative, a JVM could simply let one thread run after another without overlapping reads and writes… – Holger Mar 25 '19 at 17:12
  • In this discussion, I think people are using "re-ordering" to mean re-ordering that would be visible to the user. That is of course proscribed by the spec. The kind of re-ordering that the spec allows is re-ordering that does not cause visible changes in the behavior or output. In this question, the "visible change" the OP is asking about is the visibility of the non-volatile `int value`. Brian Goetz is quite clear that this write is visible after reading the `ready` value, so there shouldn't be any debate about the OP's question. It's visible and not re-ordered. @Holger – markspace Mar 25 '19 at 17:29
  • 1
    @markspace I think, more than often, people are using “re-ordering” without any understanding at all. This is best illustrated by the question which shows a piece of code which writes some variables, without any context, most notably without the reading end (if there is one). All this talking about reordering is not only formally wrong, it’s leading into the wrong direction. In that regard, no, it is not enough to read the `ready` variable, its value must be used in a way that ensures that the read was subsequent to the write, i.e. read `true`, like reading `value` in a conditional. – Holger Mar 26 '19 at 07:56
  • @Holger you are right, my question is missing a reading thread. The reading thread behaves as described on the listed websites and as shown in Malt's answer. – Marcono1234 Mar 27 '19 at 17:53

1 Answers1

1

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?

Malt
  • 28,965
  • 9
  • 65
  • 105
  • Thank you very much! But I still don't understand why the program order applies to (1) and (2), but would not apply if both variables were non-`volatile`. Where is this described? – Marcono1234 Feb 04 '19 at 21:09
  • It isn't described. That's why you can't know for sure what will happen, since there are no guarantees. – Malt Feb 04 '19 at 22:09