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?
running
in this case, are also updated when any volatile variable is read? I think a synchronized block would cause that. – Forbiddingvolatile
field isn't read. And yes, reading anyvolatile
field ensures a happens-before relationship for all changes that happened before due to the example in the linked paragraph: docs.oracle.com/javase/specs/jls/se8/html/… – Janikwhile( running2 || running )
then "running" never gets read because "||" is a short circuit operator. Therefore it never gets optimized out. You set running2 to false, and running2 is volatile so it has to be updated (happens-before) thenrunning
gets read for the first time and the loop ends. – Forbiddingrunning
is read correctly asfalse
(due to the happens-before relationship) as soon as it's actually read. – Janikrunning2 = false
is not required. – Limitrunning
to false, it will not stop the loop. ..." Yes, but for two reasons actually: 1) becauserunning2
is stilltrue
and 2) because the thread isn't guaranteed to seerunning
asfalse
– Janikrunning2 = false;
is required afaik to ensure a happens-before relationship. – Janikvolatile
will act as a compile-time memory barrier (like GNU Casm("" ::: "memory")
) and force the JVM to make asm that re-reads the non-volatile as well. (Because that happens after a load that might have synced-with.) Yeah, this sounds reasonable, if the compiler can't optimize away the volatile read even though its value isn't needed (only controlling an empty if). So basically the question is whether the JLS allows removing such loads, removing the only thing that could be syncing with another thread. – BroadswordThe write running2 = false; is required afaik to ensure a happens-before relationship
- where is the local value ofrunning
visible tomain
thread stored after exiting#main
method (i.e. aftermain
thread exited)? we have written it, we can't lose it. – Limitif(running2) {}
, you claim Thread doesn't finish execution because the if statement is optimized out. Therefore there is never a volatile read, so the while loop coninues execution. You're claiming this is a bug because the volatile read should act like a memory barrier and bothrunning
andrunning2
should be updated. I don't think this is a bug because I don't thinkrunning
needs to be updated when there is an update torunning2
. It would help your question if you can show that with the JLS. Also, the if example exits fine for me,openjdk version "11.0.15"
– Forbiddingif (running2) {}
could be optimized out. – Takashi