3
votes

I'm writing a multi-thread program and was researching if I should use volatile for my boolean flag. The docs oracle trail on concurrency doesn't explain anything about memory consistency errors other than:

Memory consistency errors occur when different threads have inconsistent views of what should be the same data.

It makes sense to assume that these inconsistent views only occur after a 'write'-operation. But how long after?

EXAMPLE 1

Thread A: Retrieve flag.
Thread B: Retrieve flag.
Thread A: Negate retrieved value; result is true.
Thread A: Store result in flag; flag is now true.
Thread B: System.out.print(flag) --> false

Since Thread A and Thread B runs concurrently, the print could also result in true, depending on when it retrieved flag. That makes perfect sense as for inconsistency.

But the way memory consistency errors are described (writes to a variable are not necessarily reflected in other threads) it sounds like this is also true:

EXAMPLE 2

Thread A: Retrieve flag.
Thread A: Change retrieved value; result is true.
Thread A: Store result in flag; flag is now true.
//any longer amount of time passes (while Thread A didn't explicitly happen-before Thread B, it obviously did.)
Thread B: Retrieve flag.
Thread B: System.out.print(flag) --> true OR false (unpredictable)

I strongly assume that example 2 cannot be true. The problem is that only if it is true can I see the use of volatile to establish a happens-before.

If it is true, why is it so? If it's not.. why use volatile at all?

1
I am not sure what you're asking but maybe When exactly do you use the volatile keyword in Java? answers your question.Mick Mnemonic
@MickMnemonic I have shifted through all of the questions on topic. I have indeed found a 'best' answer on when to use volatile. My question is more why?user2651804
@MickMnemonic When I was typing out this question, I simply flat out assumed that the 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.user2651804
The whole confusion starts when you say/think, it obviously did.. From your perspective it obviously did. But from a JVM perspective, if there's no explicit happens-before relationship, it didn't necessarily. So, without explicitly enforcing a happens-before relationship between Thread A storing the result back in the flag, and Thread B retrieving the flag, it is possible that Thread B returns the previous value of the flag. No matter how much time, in your perspective, has elapsed between those 2 operations. Time doesn't matter at all for these strict happens-before semantics.Bruno Reis
One important thing about your examples and tests... If you try to "visualize" the (lack of) happens-before relationships by using System.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

1 Answers

5
votes

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!