I am reading Effective Java
and in Chapter 10: Concurrency; Item 66: Synchronize access to shared mutable data, there is some code like this:
public class StopThread {
private static boolean stopRequested;
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
System.out.println(stopRequested);
Thread backgroundThread = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
int i = 0;
while (!stopRequested){
i++;
}
System.out.println("done");
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
First, I think the thread should run one second and then stop, since the stopRequested
is set to true
afterwards. However, the program never stops. It will never print done
. The author said
while (!stopRequested)
i++;
will be transformed into this:
if (!stopRequested)
while(true)
i++;
Could someone explain me this?
And another thing I find is that if I change the program to this:
public class StopThread {
private static boolean stopRequested;
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
System.out.println(stopRequested);
Thread backgroundThread = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
int i = 0;
while (!stopRequested){
i++;
System.out.println(i);
}
System.out.println("done");
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
The program runs 1 second and stops as expected. What's the difference here?
I doubt the author actually said that (exactly).
But the point is that
while (!stopRequested)
i++;
could behave like
if (!stopRequested)
while(true)
i++;
since the Java spec allows the initial value of stopRequested
to be cached in a register, or fetched from a (potentially stale) copy in the memory cache. One thread is not guaranteed to read the results of memory writes made by another thread unless there is a formal "happens before" relationship between the write and the subsequent read. In this case, there is no such relationship. That means that it is not specified whether the child thread will see the result of the parent thread's assignment to stopRequested
.
As the author of that book would have explained, the solutions include:
stopRequested
as volatile
,stopRequested
does so within a synchronized
blocks or methods that synchronizes on the same object, Lock
objects rather than synchronized
, orThen you ask why your test seemed to work.
That's is explained by the fact that while the child is not guaranteed to see the effect of the parent's assignment, it is also not guaranteed to NOT see it ... either.
Or to put it another way. The Java spec does not say which of the two possibilities will happen.
Now for a specific program, compiled by a specific compiler, run by a specific version of the JVM on specific hardware, you could find that the program behaved one way (or the other way) consistently. Maybe 99.9% of the time. Maybe even 100% of the time. But the same program compiled and run in a different context could behave differently. The JLS says so.
Another explanation for why the two almost identical versions of the program behave differently is that the System.out
PrintWriter
object is doing some internal synchronization when println
is called. That could be giving you a serendipitous "happens before".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With