The following program could loop forever because the value of ready might never become visible to the reader thread. In the absence of synchronization, the compiler, processor, and runtime can do some optimization and change the order in which memory actions happen. For this particular example, compiler, processor, and runtime could cache the value of variable "ready" for the while loop in ReaderThread, so that it always read from a cache, never notice the original variable changed value.
=====
public class NonVisibilityDemo extends Thread{
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
long elapse = System.currentTimeMillis();
while(!ready) {
//Thread.yield();
}
System.out.println("number =" + number + " at " + System.currentTimeMillis());
}
}
public static void main(String...args) throws InterruptedException {
new ReaderThread().start();
Thread.sleep(1000);
number = 42;
ready = true;
System.out.println("set ready to true at "+ System.currentTimeMillis());
}
}
======
chap3>java NonVisibilityDemo
set ready to true at 1463323507074
^Cchap3>
As if multithreading visibility issue is not illusive enough, this kind of issue has heisberg feature -- they can not be observed. When you try to observe it, observation changed the state, the phenomenon disappears.
In the following example, we added debug line:
if(System.currentTimeMillis() - elapse > WAITMILLISECONDS) {
System.out.println("ready =" + ready + " at " + System.currentTimeMillis());
}
If the while loop runs much longer than expected, we print out the exact value of the variable "ready".
However, no matter what value WAITMILLISECONDS is, whenever we are trying to print the value of variable "ready", it is always true, then the while loop exits. What happened is, observing the value of ready with println cause the variable ready updated to the latest value and thus breaks the while loop.
Why println changed the variable "ready"?
Because println is synchronized
public void println(String x) {
synchronized(this) {
this.print(x);
this.newLine();
}
}
Two sequential calls of System.out.println() in main thread and in second thread create a synchronization order between two threads. That means that all actions (in this case it is variable ready), that happened in main thread before releasing a monitor (exiting synchronized method) will be seen by the code, executed in second thread after it acquires a monitor (enter synchronized method).
In simple words, calling System.out.println() makes this synchronization.
=====
public class NonVisibilityDemo extends Thread{
private static boolean ready;
private static int number;
private static final long WAITMILLISECONDS = 10 * 1000; //change it
private static class ReaderThread extends Thread {
public void run() {
long elapse = System.currentTimeMillis();
while(!ready) {
//Thread.yield();
if(System.currentTimeMillis() - elapse > WAITMILLISECONDS) {
System.out.println("ready =" + ready + " at " + System.currentTimeMillis());
}
}
System.out.println("number =" + number + " at " + System.currentTimeMillis());
}
}
public static void main(String...args) throws InterruptedException {
new ReaderThread().start();
Thread.sleep(1000);
number = 42;
ready = true;
System.out.println("set ready to true at "+ System.currentTimeMillis());
}
}
======
chap3>java NonVisibilityDemo
set ready to true at 1463323443159
ready =true at 1463323452158
number =42 at 1463323452158
The solution is extremely simple, you just have to replace
private static boolean ready;
with
private static volatile boolean ready;
When a field is declared volatile, the compiler and runtime are put on notice that this variable is shared and that operations on it should not be recored with other memory operations. Volatile variables are not cached in registers or in caches where they are hidden from other processors, so a read of a volatile variable always returns the most recent write by any thread. Period.
=====
public class NonVisibilityDemo extends Thread{
private static volatile boolean ready;
private static int number;
private static final long WAITMILLISECONDS = 10 * 1000; //change it
private static class ReaderThread extends Thread {
public void run() {
long elapse = System.currentTimeMillis();
while(!ready) {
//Thread.yield();
if(System.currentTimeMillis() - elapse > WAITMILLISECONDS) {
System.out.println("ready =" + ready + " at " + System.currentTimeMillis());
}
}
System.out.println("number =" + number + " at " + System.currentTimeMillis());
}
}
public static void main(String...args) throws InterruptedException {
new ReaderThread().start();
Thread.sleep(1000);
number = 42;
ready = true;
System.out.println("set ready to true at "+ System.currentTimeMillis());
}
}
======
chap3>java NonVisibilityDemo
set ready to true at 1463323361135
number =415451296 at 1463323361135