One might wonder as I did if this work is still worth reading, given that Java concurrency in Practice (JCIP) is a more recent book, always recommended for concurrency in Java, and also has Doug Lea as one of its authors. And the answer is: very much so. Even though it’s a 1999 book, it is still surprisingly relevant. It shows its age at parts, e.g. in the use of raw types (generics were added in 2004 with Java 5), or when giving examples of SynchronizedInt and similar classes for what we now have AtomicInteger and siblings. But even so, as the book’s subtitle says, the book is about design principles and patterns, and luckily (or maybe obviously) these are timeless. And all this coming from a clear authority on the subject, given that he is the main author of the java.util.concurrent package (JSR 166).
Concurrent Programming in Java (CPJ) provides a mixture of theory and practice, and although the theory is solid (going over the usual topics such as safety and liveness concerns), the practice is what shines in it. The book shows different alternatives to solving problems, with many code samples, and discussions of the trade-offs of each design.
It’s likely that JCIP should be read first, in part so that the reader can more easily distinguish warnings that are no longer true (e.g. that Locks “may entail greater overhead since they are less readily optimized than are uses of built-in synchronization”). Also, because JCIP is more up-to-date. It goes over most of the java.util.concurrent utilities and when to use them, so it has more actionable advice for the average programmer.
CPJ instead relies more heavily on wait/notify patterns which are nowadays mostly discouraged in favor of the java.util.concurrent utilities (which were added later to the language thanks in great part to Doug Lea’s work). But if one needs to use wait and notify/notifyAll, I doubt there is a book with a bigger set of solid examples. Given that the book is previous to JSR 166, rather than going over the java.concurrent.package and how to use its utilities, the book deals more with how one would go about designing and implemeting such utilities, showing implementations of a Semaphore and a Semaphore with fairness guarantees (a FIFOSemaphore), a Lock (called Mutex in the book), etc.
A lot of techniques for designing and refactoring existing classes are also outlined, such as before/after designs, conflict sets, splitting locks, splitting classes, and the Specific Notification pattern, among many others. The book is also full of references, which is very useful for anyone wanting to dig deeper into the literature.