One of the most challenging things to understand about the JVM memory model is that, strictly speaking, timing (ie, your wall clock) is completely irrelevant.
No matter how long (according to your wall clock) time elapses between 2 operations in 2 separate threads, if there isn't a happens-before relationship, there are absolutely no guarantees as to what each thread will see in the memory.
In your Example 2, the tricky part is that you mention,
while Thread A didn't explicitly happen-before Thread B, it obviously did.
From the description above, the only thing that you can say is obvious is that, according to the time measurements from your wall clock, some operations happened later than others. But that doesn't imply a happens-before relationship in the strict sense of the JVM memory model.
Let me show a set of operations that are compatible with your description of Example 2 above (ie, according to the measurements made by your wall clock) and that could result in either true or false, and no guarantees can be made.
- The Main Thread M starts Thread A and Thread B: there's a happens-before relationship between Thread M and Thread A, and between Thread M and Thread B. For this reason, if nothing else happens, both Thread A and Thread B will see the same value as Thread M for that boolean. Let's assume it was initialized as
false
(to keep it compatible to your description).
Assume you are running on a multi-core machine. Also, assume Thread A was allocated in Core 1, and Thread B was allocated in Core 2.
Thread A reads the boolean value: it will necessarily read false
(see the previous bullet point). When this read happens, it might happen that some memory pages, including the one containing that boolean, will be cached into Core 1's L1 cache, or L2 cache -- any cache local to that specific core.
Thread A negates and stores the boolean value: it will store true
now. But the question is: where? Until a happens-before occur, Thread A is free to store this new value only on the local cache to the Core running the thread. Therefore, it is possible that the value will be updated on Core 1's L1/L2 cache, but will remain unchanged in the processor's L3 cache or in RAM.
After some time (according to your wall clock), Thread B reads the boolean value: if Thread A didn't flush the changes into L3 or RAM, it's entirely possible that Thread B will read false
. If Thread A, on the other hand, had flushed the changes, it is possible that Thread B will read true
(but still not guaranteed -- Thread B might have received a copy of Thread M's view of the memory and, due to the lack of happens-before, it won't go to the RAM again and will still see the original value).
The only way to guarantee anything is to have an explicit happens-before: it would force Thread A to flush its memory, and would force Thread B to read not from a local cache, but truly read it from the "authoritative" source.
Without a happens-before, as you can see form the example above, anything can happen, no matter how much time (from your perspective) elapses between events in different threads.
Now, the big question: why would volatile
solve the issues in your Example 2?
If that boolean variable is marked as volatile
, and if the interleaving of operations happens according to your Example 2 above (ie, from a wall clock perspective), then, only then, Thread B is guaranteed to see true
(ie, otherwise there are no guarantees at all).
The reason is that volatile
helps establish happens-before relationships. It goes as follows: a write to a volatile
variable happens-before any subsequent read to the same variable.
Thus, by marking the variable volatile
, if from a timing perspective Thread B reads only after Thread A updates, then Thread B is guaranteed to see the update (from a memory consistency perspective).
One very interesting fact now: if Thread A makes changes to non-volatile variables, then updates a volatile variable, then later (from a wall clock perspective) Thread B reads that volatile variable, it's also guaranteed that Thread B will see all the changes to the non-volatile variables! This is used by very sophisticated code that wants to avoid locks and still requires strong memory consistency semantics. It's typically called volatile variable piggybacking.
As a final remark, if you try to simulate the (lack of) happens-before relationships, it might be frustrating... When you write things out to the console (ie, System.out.println
), the JVM might do a lot of synchronization between multiple different threads, so a lot of memory might actually get flushed, and you won't necessarily be able to see the effects you are looking for... It's very hard to simulate all this!
console.print
in example 2 wouldn't be unpredictable. The more I consider it, the more I'm open to the possibility. If it can indeed result in memory inconsistency then my confusion is completely eliminated. – user2651804System.out.print(...)
stuff, then you might not be successful -- the act of printing out stuff to the console might cause a lot of synchronization between many internal JVM threads and even between your user threads, therefore causing memory to be flushed, and a few happens-before relationships to be established, so you won't see the lack of happens-before. It's truly hard to simulate it! – Bruno Reis