Re: Java Threads
Posted: Sep 10, 2005 8:23 PM
I've decided to split my post into two parts: First I'll quickly spit out some thoughts related to this discussion. Only then I'll add some additional comments for the interested. Of course this post only reflects my personal impressions and understanding. Although I have spent quite some time trying to master Java threading (both in Java 1.4 and 1.5), I'm certainly not an expert on the topic. Feel free to correct me any time. That said, let's start!
1) "Java Threads" (3rd Edition) contains many unclear explanations, inaccuracies and mistakes.
2) A single access to (read of or write to) a volatile variable is always atomic. This includes volatile variables whose associated type is a reference type, long or double, and holds both for Java 1.4 and 1.5.
3) As was pointed out already, an increment or decrement operation amounts to two variable accesses (a read followed by a write). Such an operation <b>must</b> be synchronized to be thread-safe (declaring the variable "volatile" is not enough).
4) I agree with an earlier writer that the notion of "happens-before" helps a lot to understand threading in Java 1.5.
5) Understanding "volatile" and "final" wrt. threading should be easier in Java 1.5 than it was in 1.4. One of the main motivations behind the changes was to bring their semantics more in line with people's intuition. In both cases semantics were strengthened, meaning that everything that worked in 1.4 will still work (and probably also make sense) in 1.5.
6) Generalizing on the last statement, all programs that were thread-safe in Java 1.4 are also thread-safe in 1.5.
7) To better understand "volatile" in Java 1.5, the following comparison might help: Declaring a variable x "volatile" is equivalent to wrapping all operations on x in synchronized blocks sharing the same lock object, except that "volatile" doesn't enforce mutual exclusion.
Now for the comments:
ad 1) Threading is a very delicate topic. Therefore I expect any good publication in this field to be particularly clear, accurate and correct. "Java Threads" clearly fails in this respect. Hopefully there will soon be a better work on threading in Java 1.5.
ad 2) This is one of the points where the authors of "Java Threads" err. They claim on several occasions that an access to a volatile long/double value is not atomic. For example, in chapter 5.2 they say the following: "Volatile variables can be safely used only for a single load or store operation and can't be applied to long or double variables." However, a quick look into chapter 17.7 of the JLS proves them wrong: "Writes and reads of volatile long and double values are always atomic."
ad 4) "happens-before" describes a relationship between certain program actions such as variable reads, variable writes, monitor locks and monitor unlocks. (In mathematical terms, happens-before is a partial order relation.) It lets us reason about programs in the following way: Given two actions A and B, only if A "happens-before" B it is guaranteed that B sees the effects of A. In other words, if we want to ensure that B sees the effects of A, we have to structure our program in such a way that A happens-before B. For example, A happens-before B if:
- A and B are actions of the same thread and A comes before B (according to the semantics of single-threaded programs)
- A is a write to a volatile variable and B is a subsequent read of the same variable (possibly in a different thread)
For a more exhaustive and accurate description, see the JLS.
ad 7) It is a common misconception that the sole purpose of synchronized blocks is to enforce mutual exclusion. In fact, they also help to control issues of visibility and ordering.
One more try to explain the semantics of "volatile" in Java 1.5: In addition to the globally shared main memory, each thread maintains its own working memory (an abstraction of registers, caches, etc.). If a thread is to access a volatile variable, it first invalidates its working memory, then it accesses the variable, then it flushes its working memory. Note that this might also have effects on other variables: Suppose we have two threads named A and B operating on shared variable x. If A writes x and B subsequently reads x, B will not only see A's write to x, but also all prior writes by A to other shared variables (assuming none of them was meanwhile written to by yet another thread). This has changed from Java 1.4, where B was only guaranteed to see A's write to x.
Finally some resources on Java threading that I found particularly enlightening:
Brian Goetz: Java theory and practice: Fixing the Java Memory Model, Part 1
Brian Goetz: Java theory and practice: Fixing the Java Memory Model, Part 2
Doug Lea: Concurrent Programming in Java: Design Principles and Patterns (2nd Edition)
The Java Language Specification, 3rd Edition
Freely available at http://java.sun.com/docs/books/jls/
PS: Thanks to Bruce Eckel for the great "Thinking in Java" books! I'm already looking forward to the next edition.