In chapter 17 of Java language specification, there is a section explaining why "happens-before consistency is not sufficient". And here is the example:
At first, x = y = 0
Thread 1 | Thread 2
r1 = x; | r2 = y;
if (r1 != 0) y = 1; | if (r2 != 0) x = 1;
And here is a possbile execution trace:
r1 = x; // sees write of x = 1
y = 1;
r2 = y; // sees write of y = 1
x = 1;
How could this happen ? What I am confusing is that, when the first action sees that x = 1, doesn't it mean that the condition r2 != 0 has become true and thus y has been assigned to 1 ? But in that order, y = 1 comes after r1 = x. Where did I make a mistake to understand the example ? And how should I interpret this example correctly?
I believe the point made by this example in the Java spec is the one that Hans Boehm et al. write about in Outlawing Ghosts, and it points to a deficiency in the memory models of some contemporary languages (Java, C++11, and even C++14 still, which addressed but did not resolve this issue).
The point is this: The program, as written, is correctly synchronized by the rules of the language. (The same is true in C++ if you use atomic variables and memory_order_relaxed
everywhere.) However, it is still not forbidden for unexpected behaviour to occur. To paraphrase Boehm: The machine could speculate on the value of, say, x
being 1, then execute the resulting branch, and later (presumably when the memory finally responded) verify that the speculation was true. Finding the speculation true indeed, since in the meantime the other thread did indeed store x = 1
, the machine continues and does not roll back the speculated execution.
What's worse is that the CPU could really have speculated any value to exist. Consider this modified example:
r1 = x | r2 = y
if (r1 != 0) y = r1 | if (r2 != 0) x = r2
In this case, x
and y
could end up with any value, for the same reason. The machine could speculate the value to be anything, then speculatively continue execution with that assumption, and later find its speculation to be true, in the proverbial self-fulfilling prophecy.
It is perhaps reassuring that no real hardware at present behaves like this. But the point is that the memory models of contemporary languages do not forbid this behaviour. The section you cited is Java's attempt at saying, "look, we require happens-before consistency, but this other weird thing over here still shouldn't happen". C++14 has a similarly vague opinion on this problem in the non-normative note 1.10/25.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With