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?


