question about "Java Concurrency in Practice" example
I'm looking at a code sample from "Java Concurrency in Practice" by Brian Goetz. He says that it is possible that this code will stay in an infinite loop because "the value of 'ready' might never become visible to the reader thread". I don't understand how this can happen...
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready)
Thread.yield();
System.out.println(number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
Because ready
isn't marked as volatile
and the value may be cached at the start of the while
loop because it isn't changed within the while
loop. It's one of the ways the jitter optimizes the code.
So it's possible that the thread starts before ready = true
and reads ready = false
caches that thread-locally and never reads it again.
Check out the volatile keyword.
The reason is explained in the section following the one with the code sample.
3.1.1 Stale data
NoVisibility
demonstrated on of the ways that insufficiently synchronized programs can cause surprising results: stale data. When the reader thread examinesready
, it may see an out-of-date value. Unless synchronization is used every time a variable is accessed, it is possible to see a stale value for that variable.
The Java Memory Model allows the JVM to optimize reference accesses and such as if it is a single threaded application, unless the field is marked as volatile
or the accesses with a lock being held (the story gets a bit complicated with locks actually).
In the example, you provided, the JVM could infer that ready
field may not be modified within the current thread, so it would replace !ready
with false
, causing an infinite loop. Marking the the field as volatile
would cause the JVM to check the field value every time (or at least ensure that ready
changes propagate to the running thread).
The problem is rooted in the hardware -- each CPU has different behavior with respect to cache coherence, memory visibility, and reordering of operations. Java is in better shape here than C++ because it defines a cross-platform memory model that all programmers can count on. When Java runs on a system whose memory model is weaker than that required by the Java Memory Model, the JVM has to make up the difference.
Languages like C "inherit" the memory model of the underlying hardware. There is work afoot to give C++ a formal memory model so that C++ programs can mean the same thing on different platforms.
private static boolean ready;
private static int number;
The way the memory model can work is that each thread could be reading and writing to its own copy of these variables (the problem affects non-static member variables too). This is a consequence of the way the underlying architecture can work.
Jeremy Manson and Brian Goetz:
In multiprocessor systems, processors generally have one or more layers of memory cache,which improves performance both by speeding access to data (because the data is closer to the processor) and reducing traffic on the shared memory bus (because many memory operations can be satisfied by local caches.) Memory caches can improve performance tremendously, but they present a host of new challenges. What, for example, happens when two processors examine the same memory location at the same time? Under what conditions will they see the same value?
So, in your example, the two threads might run on different processors, each with a copy of ready
in their own, separate caches. The Java language provides the volatile
and synchronized
mechanisms for ensuring that the values seen by the threads are in sync.