Happens-before relationships with volatile fields and synchronized blocks in Java - and their impact on non-volatile variables?
Asked Answered
P

3

54

I am still pretty new to the concept of threading, and try to understand more about it. Recently, I came across a blog post on What Volatile Means in Java by Jeremy Manson, where he writes:

When one thread writes to a volatile variable, and another thread sees that write, the first thread is telling the second about all of the contents of memory up until it performed the write to that volatile variable. [...] all of the memory contents seen by Thread 1, before it wrote to [volatile] ready, must be visible to Thread 2, after it reads the value true for ready. [emphasis added by myself]

Now, does that mean that all variables (volatile or not) held in Thread 1's memory at the time of the write to the volatile variable will become visible to Thread 2 after it reads that volatile variable? If so, is it possible to puzzle that statement together from the official Java documentation/Oracle sources? And from which version of Java onwards will this work?

In particular, if all Threads share the following class variables:

private String s = "running";
private volatile boolean b = false;

And Thread 1 executes the following first:

s = "done";
b = true;

And Thread 2 then executes afterwards (after Thread 1 wrote to the volatile field):

boolean flag = b; //read from volatile
System.out.println(s);

Would this be guaranteed to print "done"?

What would happen if instead of declaring b as volatile I put the write and read into a synchronized block?

Additionally, in a discussion entitled "Are static variables shared between threads?", @TREE writes:

Don't use volatile to protect more than one piece of shared state.

Why? (Sorry; I can't comment yet on other questions, or I would have asked there...)

Protozoan answered 14/6, 2013 at 12:30 Comment(1)
Possible dupe: #12438964Extortion
P
41

Yes, it is guaranteed that thread 2 will print "done" . Of course, that is if the write to b in Thread 1 actually happens before the read from b in Thread 2, rather than happening at the same time, or earlier!

The heart of the reasoning here is the happens-before relationship. Multithreaded program executions are seen as being made of events. Events can be related by happens-before relationships, which say that one event happens before another. Even if two events are not directly related, if you can trace a chain of happens-before relationships from one event to another, then you can say that one happens before the other.

In your case, you have the following events:

  • Thread 1 writes to s
  • Thread 1 writes to b
  • Thread 2 reads from b
  • Thread 2 reads from s

And the following rules come into play:

  • "If x and y are actions of the same thread and x comes before y in program order, then hb(x, y)." (the program order rule)
  • "A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field." (the volatile rule)

The following happens-before relationships therefore exist:

  • Thread 1 writes to s happens before Thread 1 writes to b (program order rule)
  • Thread 1 writes to b happens before Thread 2 reads from b (volatile rule)
  • Thread 2 reads from b happens before Thread 2 reads from s (program order rule)

If you follow that chain, you can see that as a result:

  • Thread 1 writes to s happens before Thread 2 reads from s
Preterit answered 14/6, 2013 at 12:52 Comment(13)
I don't see where this documentation states that if a is assigned before some volatile variable b then both variables are synchronized between two threads. Is there something in the data model that says the when a volatile variable is modified all other variables are synchronized between the threads as opposed to just the one volatile variable?Undine
Indeed there is! I have expanded my answer to attempt to explain this. Apologies for posting the answer without the explanation; i was dragged off to a meeting.Preterit
Ah. OK. So the happens before rule applies to all variables (volatile or not) between threads on one variable is volatile. Thanks. Learn something new every day.Undine
No problem. This is one of those features of the language that is described in plain sight in the language specification, but in a bit of the specification that nobody ever reads, and so which is not very well known!Preterit
@Noofiz: It's certainly surprising to anyone who is not familiar with concurrent programming. Readability for a general audience is probably improved by sticking to synchronized blocks for this. But i would not agree that it is wrong.Preterit
One of the great pieces of modern concurrent software engineering is the Disruptor, which relies on volatile to achieve both safety and efficiency: mechanitis.blogspot.co.uk/2011/08/…Preterit
The answer is correct regarding the cited blog statement. This statement correctly says that the reader thread must have read the value true from the boolean variable. But the code example of the question does not check this. So for the question’s code example the answer must be “No, it is not guaranteed to print "done"”.Aney
@Holger: That is a very good point! I think i took "after Thread 1 wrote to the volatile field" as meaning something like "long enough after Thread 1 wrote to the volatile field that Thread 2 will see the write", but i should certainly make that explicit.Preterit
Actually I wouldn't be so categorical in claiming any guarantees for this example. It works only if Thread 1 and 2 are the only ones involved and further only if the shown actions are the only ones done by these threads. A much stronger guarantee would hold for a different example where we have class MyBean { final String msg; final boolean status;} and some volatile MyBean b;, then in Thread 1 b = new MyBean("done", true); and in Thread 2 MyBean b1 = b;. This is how safe publishing over volatile is properly implemented in practice.Firecure
@MarkoTopolnik You're right that this only works under those circumstances. It also only works if the JVM is implemented correctly and nobody turns the computer off halfway through. All these sorts of thing are usually taken as given in hypothetical questions like this.Preterit
Behind each hypothetical question is a very practical concern: that of learning from it and applying it by generalization to a real problem.Firecure
i think of it as cache update...every time a thread writes to volatile it flushes the cache to main memory and if a read is performed on that volatile after thread1 writes, its gonna update its cache(thread 2) and hence it will see the updated variables which got written before volatile variable along with it. (not the ones written after volatile write.) this is the reason reordering not allowed when volatile is involved. if both are performed at the same time(read and write) its gonna face some concurrency related issue. please upvote if this helped.Kamakura
Program order is not a rule. Example 17.4.1 demonstrates "However, compilers are allowed to reorder the instructions in either thread, when this does not affect the execution of that thread in isolation." So your statement "Thread 1 writes to s happens before Thread 1 writes to b (program order rule)" if wrong. There is no program order rule, program order is effective when your program is properly synchronized.Vanadinite
F
18

What would happen if instead of declaring b as volatile I put the write and read into a synchronized block?

If and only if you protect all such synchronized blocks with the same lock will you have the same guarantee of visibility as with your volatile example. You will in addition have mutual exclusion of the execution of such synchronized blocks.

Don't use volatile to protect more than one piece of shared state.

Why?

volatile does not guarantee atomicity: in your example the s variable may also have been mutated by other threads after the write you are showing; the reading thread won't have any guarantee as to which value it sees. Same thing goes for writes to s occurring after your read of the volatile, but before the read of s.

What is safe to do, and done in practice, is sharing immutable state transitively accessible from the reference written to a volatile variable. So maybe that's the meaning intended by "one piece of shared state".

is it possible to puzzle that statement together from the official Java documentation/Oracle sources?

Quotes from the spec:

17.4.4. Synchronization Order

A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).

17.4.5. Happens-before Order

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

If an action x synchronizes-with a following action y, then we also have hb(x, y).

This should be enough.

And from which version of Java onwards will this work?

Java Language Specification, 3rd Edition introduced the rewrite of the Memory Model specification which is the key to the above guarantees. NB most previous versions acted as if the guarantees were there and many lines of code actually depended on it. People were surprised when they found out that the guarantees had in fact not been there.

Firecure answered 14/6, 2013 at 13:0 Comment(6)
please explain this in simple terms."What is safe to do, and done in practice, is sharing immutable state transitively accessible from the reference written to a volatile variable. So maybe that's the meaning intended by "one piece of shared state". i don't understand...what is immutable state transitively.Kamakura
It is "transitively accessible" from the volatile variable. var.x.y -- here y is transitively accessible from var.Firecure
"If x and y are actions of the same thread and x comes before y in program order, then hb(x, y)." I don't get this statement. It is not true for other threads observing the actions. Further down in the same section they say, "More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads."Vanadinite
@Vanadinite Your second quote is exactly the answer. If there are actions in thread 1: x1 and y1; and there are actions in thread2: x2, y2; then hb(x1, y1) and hb(x2, y2) hold by program order, but neither hb(x1, x2) nor hb(x2, x1) holds because these are two independent threads.Firecure
I read it as, actions x1 and y1 in Thread1 have an hb(x1, y1) relationship, but they do not have a happens-before relationship in for reads in Thread2.Vanadinite
@Vanadinite Whether or not they have a hb-relationship between them makes no difference to Thread2, which does not have a hb-relationship with any of them. The fact that there is a hb(x1, y1) is unobservable from Thread2.Firecure
D
15

Would this be guaranteed to print "done"?

As said in Java Concurrency in Practice:

When thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable.

So YES, This guarantees to print "done".

What would happen if instead of declaring b as volatile I put the write and read into a synchronized block?

This too will guarantee the same.

Don't use volatile to protect more than one piece of shared state.

Why?

Because, volatile guarantees only Visibility. It does'nt guarantee atomicity. If We have two volatile writes in a method which is being accessed by a thread A and another thread B is accessing those volatile variables , then while thread A is executing the method it might be possible that thread A will be preempted by thread B in the middle of operations(e.g. after first volatile write but before second volatile write by the thread A). So to guarantee the atomicity of operation synchronization is the most feasible way out.

Decker answered 14/6, 2013 at 13:9 Comment(2)
"..the values of all variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable". Yes, sometimes is called "pyggy-backing" on synhronizations, frequently used for concurency optimizations. So YES, This guarantees to print "done".Phylloxera
@IvanVoroshilin Thanks for the valuable insight about "pyggy-backing". Can you please provide a link to this fact for further details?Decker

© 2022 - 2024 — McMap. All rights reserved.