First of all, this:
The reading and writing instructions of volatile variables cannot be reordered by the JVM...
means that volatile themselves can not be re-ordered (volatile with volatile that is); but with the caveat that
the JVM may reorder instructions for performance reasons as long as the JVM detects no change in program behaviour from the reordering.
In general, reasoning about re-orderings (that could or not be done) by a JVM
is incorrect (I've read that instructions around volatiles are not reordered ...). Re-ordering/barriers/etc are not part of the JLS
; instead it acts on the premises of happens-before
rules and that is the only thing you should care about.
Your example, can indeed be simplified as said in the comments to:
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE, desc = "don't care about this one")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "the one we care about")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "don't care about this one")
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE, desc = "don't care about this one")
@JCStressTest
@State
public class VolatileTest {
private volatile int guard = 0;
private int x = 0;
@Actor
void writeActor() {
guard = 1; // volatile store
// your reasoning is that these two operations should be re-ordered
// unfortunately, this is not correct.
x = 1; // plain store
}
@Actor
void readActor(II_Result r) {
r.r1 = x; // plain store
r.r2 = guard; // plain store
}
}
Running this will result in 1, 0
indeed, which means that x = 1
was indeed re-ordered with guard = 1
; actually in reality many more other things could have happened (but for simplicity we call them re-orderings, even though this is not the only reason why you could observe [1, 0]
).
In JLS terms : you have not established any happens before between these operations (like a typical volatile store/volatile load) - as such those operations are free to float around. And that could be the end of the answer, pretty much. A little bit broader explanation would be that volatile
(since you used it), is said:
A write to a volatile field happens-before every subsequent read of that same field.
You are not doing any reads of the volatile guard
, so nothing is guaranteed. Another way to explain it would be this excellent article or even this one. But even if you did read guard
, still nothing is guaranteed about re-orderings because the way your code is set-up.
volatile
only works correctly when there are pairs of its usage, that is Thread1
does a write to a volatile
field - Thread2
observes the write. In this case everything that was done before the write in program order will be seen by Thread2
(obviously after it has seen that written value). Or in code:
public class VolatileTest {
private volatile int guard = 0;
private int x = 0;
@Actor
void writeActor() {
// store comes "before" the store to volatile
// as opposed to the previous example
x = 1; // plain store
guard = 1; // volatile store
}
@Actor
void readActor(II_Result r) {
r.r1 = guard; // plain store
r.r2 = x; // plain store
}
}
You are now guaranteed by the JLS
that if you see guard
to be 1
, you will also observe x
to be 1
(x = 1
can not be re-ordered below guard = 1
this time). As such, 1, 0
now is illegal and thus never seen in the output.