This question already has an answer here:
I have a simple TestThreadClientMode
class to test a race condition. I tried two attempts:
- When I run the following code with
System.out.println(count);
commented in the second thread, the output was:
OS: Windows 8.1
flag done set true
...
and the second thread was alive forever. Because the second thread never sees change of the done
flag which was set true by Main thread.
When I uncommented
System.out.println(count);
the output was:OS: Windows 8.1 0 ... 190785 190786 flag done set true Done! Thread-0 true
And the program stopped after 1 second.
How did System.out.println(count);
make the second thread see the change in done
?
Code
public class TestThreadClientMode {
private static boolean done;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
public void run() {
int count = 0;
while (!done) {
count ++;
//System.out.println(count);
}
System.out.println("Done! " + Thread.currentThread().getName() + " " + done);
}
}).start();
System.out.println("OS: " + System.getProperty("os.name"));
Thread.sleep(1000);
done = true;
System.out.println("flag done set true ");
}
}
The
println()
implementation contains explicit memory barrier:Which causes the invoking thread to refresh all variables.
The following code will have the same behavior as yours:
In fact any object can be used for monitor, following will also work:
Another way to create explicit memory barrier is using
volatile
, so the following will work:You are witnessing a side effect of println; your program is suffering from a concurrent race condition. When coordinating data between CPUs it is important to tell the Java program that you want to share the data between the CPUs, otherwise the CPUs are free to delay communication with each other.
There are a few ways to do this in Java. The main two are the keywords 'volatile' and 'synchronized' which both insert what hardware guys call 'memory barriers' into your code. Without inserting 'memory barriers' into the code, the behaviour of your concurrent program is not defined. That is, we do not know when 'done' will become visible to the other CPU, and it is thus a race condition.
Here is the implementation of System.out.println; notice the use of synchronized. The synchronized keyword is responsible for placing memory barriers in the generated assembler which is having the side effect of making the variable 'done' visible to the other CPU.
The correct fix for your program, is to place a read memory barrier when reading done and a write memory barrier on writing to it. Typically this is done by reading or writing to 'done' from within a synchronized block. In this case, marking the variable
done
asvolatile
will have the same net effect. You can also use anAtomicBoolean
instead ofboolean
for the variable.This is a brilliant example of memory consistency errors. Simply put, the variable is updated but the first thread does not always see the variable change. This issue can be solved by making
done
variablevolatile
by declaring it like so:In this case, changes to the variable are visible to all threads and the program always terminates after one second.
Update: It appears that using
System.out.println
does indeed solve the memory consistency issue - this is because the print function makes use of an underlying stream, which implements synchronization. Synchronization establishes a happens-before relationship as described in the tutorial I linked, which has the same effect as the volatile variable. (Details from this answer. Also credit to @Chris K for pointing out the side effect of the stream operation.)