Re: Volatile happens before question
On Tue, 17 Jan 2012 04:04:52 -0800 (PST), raphfrk@gmail.com wrote:
The spec says that all writes to volatiles can be considered to happen
before all subsequent reads. What does "subsequent" mean, is that
with regards to real time?
Essentially, yes. I believe the exact language talks about synchronization
order" or something, but the basic idea is the order in which program
statements are effectively executed.
Without volatile, the program statements may be effectively reordered
according to the needs of optimization (by the JIT compiler, runtime,
hardware, etc.) as long as the same outcome occurs for a given thread.
With volatile, this strengthens the requirements slightly by also requiring
the same outcome for certain parts of the code even across multiple threads
(i.e. those parts that use fields marked "volatile"). (And in particular,
in the way that the spec describes using the "happens before" language).
So,
Thread 1
int b = 0;
volatile boolean a = false;
...
...
b = 1;
a = true;
Thread 2
if (a) {
System.out.println("The value of b is " + b);
}
Since the setting of a to true happens before the reading of a as
true, the println must happen after b is set to 1.
Yes, that's precisely the typical usage of "volatile". A sentinel value
like "a" is set to some new value that a different thread will see.
Everything else that was written before that write to the "volatile" field
is guaranteed to have actually taken place before some other thread reads
from the "volatile" field.
This means that either nothing will be printed or "The value of b is
1" will be printed.
Right.
Does this work in reverse too?
For example,
Thread 1
int b = 0;
volatile boolean a = false;
...
...
a = true;
b = 1;
Thread 2
int bStore = b;
if (!a) {
System.out.println("The value of b is " + bStore);
}
Will this always print either "The value of b is 0" or nothing.
No, I don't think that you get the opposite guarantee.
Using "volatile" creates a "synchronizes-with" relationship between the
assignment "a = true" and the read "if (!a)".
Within a thread, you're guaranteed statements happen in program order.
With "volatile", you also get the guarantee that the write to the variable
"a" synchronizes with the read from variable "a". That is, that everything
that happens before the write to "a" also must happen before the read from
"a".
But there's not a "happens-after" guarantee, just a "happens-before". The
specification assures you that anything that happened before the write,
will be seen by another thread after it reads from the same volatile
variable. But since the assignment "b = 1" occurs after the assignment "a
= true", and since "b" is not volatile, the specification offers no
guarantees about what "happens-before" the write to "b" (such as the
assignment "a = true").
Without this guarantee, the write to "b" is allowed to be reordered, and
could move ahead of the write to "a".
If you want a more robust synchronization, then you have "synchronized" and
the concurrency-related objects to provide stronger guarantees.
(bStore = b) happens before (read a as false)
(read a as false) happens before (set a = true) [is this valid?]
(set a = true) happens before (set b = 1)
It's this last assertion that fails. It's only valid when looking only at
the thread in which those statements appear. You can be sure that in the
first thread, the variable "b" will not be seen to be set to "1" before the
variable "a" is set to "true". But from the point of view of other
threads, there's nothing that guarantees "a = true" happens before "b = 1".
The change to variable "a" could get published late or the change to
variable "b" could get published earlier, and either would invalidate the
apparent order of execution.
So, bStore = b happens before set b = 1, so bStore = 0.
Effectively, the rule would be "A read to a volatile happens before
the write to that volatile that overwrites the value that was read".
I'm not aware of any such rule or its equivalent appearing in the
specification.
However, that wasn't clear from the spec. I think since read/writes
to volatiles are synchronization actions, then when running the
program, they can be considered to have happened in some ordering
(consistent with program order in the threads). As long as the
program works no matter what the ordering is picked, then it is fine.
I think the key is that you get "happens-before" guarantees but not
"happens-after" guarantees. If you want a guarantee about what happens
before a write to a variable, such as in "b = 1", then "b" is what needs to
be volatile.
All that said, this question highlights the caution one must use when using
"volatile". Fact is, for most of us, using full synchronization always is
likely the best approach, as it's a much simpler concept to understand (and
frankly is not all that simple itself). "volatile" exists and can be used
when absolutely needed, but one should be prepared to really dig in and
make sure one understands exactly how the memory model is defined, to
carefully validate any concurrent code in the program, and to do rigorous
performance testing to make sure that the weakened memory guarantees really
are providing the performance benefit over simpler synchronized code that
is hoped for when one uses "volatile".
That's a pretty tall order, and most often probably not worth the cost.
Pete