The following code sample shows a common way to demonstrate concurrency issues caused by a missing happens-before relationship.
private static /*volatile*/ boolean running = true;
public static void main(String[] args) throws InterruptedException {
new Thread() {
@Override
public void run() {
while (running) {
// Do nothing
}
}
}.start();
Thread.sleep(1000);
running = false;
}
If running
is volatile
, the program is guaranteed to terminate after approximately one second. However, if running
isn't volatile
, the program isn't guaranteed to terminate at all (since there is no happens-before relationship or guarantee for visibility of changes to the variable running
in this case) and that's exactly what happens in my tests.
According to JLS 17.4.5 one can also enforce a happens-before relationship by writing to and reading another volatile
variable running2
, as shown in the following code sample.
private static boolean running = true;
private static volatile boolean running2 = true;
public static void main(String[] args) throws InterruptedException {
new Thread() {
@Override
public void run() {
while (running2 || running) {
// Do nothing
}
}
}.start();
Thread.sleep(1000);
running = false;
running2 = false;
}
The volatile
variable running2
is read in each loop iteration and when it is read as false
after approximately one second, it is also guaranteed that the variable running
is read as false
subsequently, due to the happens-before relationship. Thus the program is guaranteed to terminate after approximately one second and that's exactly what happens in my tests.
However, when I put the read of the variable running2
into an empty if
statement inside the while
loop, as shown in the following code sample, the program doesn't terminate in my tests.
private static boolean running = true;
private static volatile boolean running2 = true;
public static void main(String[] args) throws InterruptedException {
new Thread() {
@Override
public void run() {
while (running) {
if (running2) {
// Do nothing
}
}
}
}.start();
Thread.sleep(1000);
running = false;
running2 = false;
}
The idea here is that a volatile
read of running2
is like a compiler memory barrier: the compiler has to make asm that re-reads non-volatile
variables because the read of running2
might have synchronized-with a release operation in another thread. That would guarantee visibility of new values in non-volatile variables like running
.
But my JVM seems not to be doing that. Is this a compiler or JVM bug, or does the JLS allow such optimizations where a volatile
read is removed when the value isn't needed? (It's only controlling an empty if
body, so the program behaviour doesn't depend on the value being read, only on creating a happens-before relationship.)
I thought the JLS applies to the source code and since running2
is volatile
, the effect of reading the variable shouldn't be allowed to be removed due to an optimization. Is this a compiler or JVM bug, or is there a specification, which actually allows such optimizations?