by Laurence Vanhelsuwรฉ

The Java Threads API makes it to print media

news
Jul 1, 199711 mins
ConcurrencyJavaJava SE

Read this detailed review of O'Reilly's 'Java Threads' to find out if you should invest in a copy

Java Threads isnโ€™t your run of the mill Teach Yourself Java in X Days type of book. As Scott Oaks and Henry Wong, the authors of Java Threads, explain, โ€œThis book belongs to the second wave of Java books: because it covers only a single topic, it has the luxury of explaining in deeper detail how Javaโ€™s threads can be used.โ€ And this book does indeed delve into deep detail. Considering that Javaโ€™s multithreading capabilities are neatly encapsulated in barely more than a single class โ€” java.lang.Thread and class Objectโ€˜s wait() and notify() methods โ€” and are supported by the language itself via a single keyword, synchronized, it follows that Java Threadsโ€˜s 250-odd pages explore many a dark multithreaded corner.

In this review Iโ€™ll provide a blow-by-blow account of each of the eight chapters and four appendices, followed by a few miscellaneous items of note for anyone considering purchasing this book.

Java Threads

http://www.ora.com/catalog/jthreads/

ISBN 1-56592-216-6, 268 pages 9.95

Chapter and verse

Chapter 1 is the obligatory introduction to the subject matter. It explains the difference between multitasking and multithreading and why, contrary to most other programming languages, threading is so important to Java programming. The answer to the latter question simply being that all Java I/O is synchronous and that you need extra threads to avoid your applications screeching to an immediate halt whenever they access the Internet, read files, or wait for some form of timeout.

Chapter 2, โ€œThe Java Threading API,โ€ introduces the Thread class and the associated, pivotal Runnable interface. The authors examine the start(), stop(), and sleep() using a simple, multithreaded applet example, and then switch the focus to the join() method, which is used to wait for the completion of some other thread.

At this point, I think it is worth mentioning two things about the examples provided with this book: first, the book does not include a disk (floppy or CD) of source code. My initial response to this discovery was quite positive, figuring Iโ€™d just head over to the Oโ€™Reilly Web site and pick up whatever I needed; unfortunately, there is no link to such a site. My second gripe is that many of the programs are incomplete (that is, they wonโ€™t compile because they are not meant to be compilable examples) or hypothetical in nature (where 90 percent of the program is left to the readerโ€™s imagination). In short, the examples do not provide insight into the behavior of running threads. Worse, because you cannot check any of the examples, you are sometimes left reading large amounts of text and code โ€œon faith.โ€

Chapter 2 also introduces the threading pitfall par excellence: race conditions. Readers follow along as the authors develop an AsyncReadSocket class that loses data due to a (purposely) naive lack of protection against multithreaded access of this network programming class. (Yes, the authors are Unix programmers, so they think nothing of relying on your development platform being permanently connected to a TCP/IP network, or using some advanced socket programming to make a point about multithreading. To mortals with dial-up access or novices in the art of TCP/IP programming: tough luck.)

Chapter 3, โ€œSynchronization Techniques,โ€ uses a much more universally understandable example to explain the core concept of thread synchronization. The authors use the classic example of simultaneous access to a bank account to illustrate how you can eliminate race conditions by protecting some critical code sections using Javaโ€™s synchronized keyword.

The synchronized mechanism lies at the heart of most of Javaโ€™s inter-thread communication solutions. The synchronized keyword turns a code block or an entire method into a critical section by requiring the executing thread to obtain a lock on the current object before entering the synchronized code. On exiting the code, the lock is released.

Right about this time the discussion takes a confusing turn. Right after elegantly solving the banking exampleโ€™s race condition (to complete satisfaction) by simply making the methods that manipulate the shared bank account data synchronized, the authors suddenly attempt to write a BusyFlag class whose objects can be used as synchronization primitives โ€” without using the newly found hero (the synchronized keyword). Not surprisingly, they conclude that their BusyFlag class contains a race condition (it is impossible to write any synchronization mechanism from scratch without relying on Javaโ€™s synchronized keyword in one way or other).

While introducing the concept of deadlock and progressing into chapter 4, โ€œWait and Notify,โ€ this BusyFlag class goes through several confusing incarnations, which the authors themselves admit continues to harbor a race condition. I found that the contrived implementation and โ€œenhancementsโ€ of this class significantly harmed an otherwise clearly written section whose sole intent was to demonstrate that Javaโ€™s synchronized keyword can only provide fine-grained protection up to method scope. When an entire transaction needs to be atomic (to prevent race conditions occurring in entire subsystems, for example), you need to craft your own higher-level semaphore or mutex class. And that is what the BusyFlag class was all about. Or rather, the class is reused or referred to in several more examples in the remainder of the book.

With chapter 5, โ€œUseful Examples of Java Thread Programming,โ€ all the jargon, concepts, and API explained to this point is put into practice. The chapter starts by developing some thread-safe data structures like a MessageQueue, SharedMemory, and a CircularList. Then we dive back into network programming with a generic TCPServer class. Another interesting example is the AsyncInputStream class, which allows you to read from any InputStream in an asynchronous fashion. In a nutshell, the class spawns a thread to do the real stream reading (and can therefore block right there and then), while clients of the class can safely read from the input buffer that this thread constantly attempts to fill up, without ever blocking. Aside from being a nice multithreading example, the issues of caching EOF and thrown exceptions was also enlightening.

Chapter 6, โ€œJava Thread Scheduling,โ€ ponders why the Java language does not specify how threads are scheduled (a preemptive scheme, or time-sliced, or a more primitive run-to-completion scheme), and discusses how this impacts your multithreaded applications. A platform-dependent (read: serious) problem occurs if a multithreaded Java program contains CPU-bound threads (like a time-consuming rendering thread). Because Java does not guarantee time-sliced scheduling of threads (when of equal priority), any CPU-bound threads could starve all other threads from access to the CPU. This only occurs on Java host platforms like Unix where the host OS usually does not use preemptive scheduling. In other situations it may be more fair to let threads run to their logical completion to optimize average server performance in a client-server system. Either way, Javaโ€™s lack of an explicit scheduling approach means that you need to overlay your own application-level scheduler on top of Javaโ€™s. This is exactly what readers get to do in chapter 6 โ€” develop class CPUScheduler: a time-slicing, round-robin scheduler, the first incarnation of which is a thoroughly impressive sixteen lines of Java code (now try beating that in C++!).

For the remaining 20 pages, readers make various improvements to the initial scheduler, ultimately resulting in relying on thread priorities to manage a pool of threads submitted to the CPUScheduler.

Chapter 7, โ€œAdvanced Synchronization Topics,โ€ further studies deadlocking scenarios like lock starvation. This chapter actually contains very interesting high-level, language-independent analyses of which factors are needed for certain types of deadlock to occur. The chapter also discusses reader-writer locks as a form of lock that has been optimized to grant multiple access to reading threads, but only allow access to a single writing thread.

The final (and quite brief) chapter, โ€œThread Groups,โ€ focuses on the java.lang.ThreadGroup class and its uses. Because class ThreadGroup lets applications, and more specifically applets, have low-level information on potentially security-compromising systems (like the garbage-collecting thread), the authors include a discussion of thread security issues and the central role of the SecurityManager class.

The remaining 40 pages of Java Threads is devoted to its four appendices. Appendix A combines several topics too small to justify a chapter on their own. Youโ€™ll find information on the interrupt() method, the ThreadDeath class, the thread execution stack, and how you can write your own default exception handler for exceptions that are not caught by your run() method. Appendix B enumerates some of the more important exceptions and errors that can occur when programming with more than one thread.

Appendix C is a very interesting look under the hood of the Java API and analyses how the API itself relies on multithreading to manage timing, garbage collection, object finalization, AWT event dispatching, AWT window refresh, playing audio, and loading and displaying images. The appendix climaxes with an eye-opening table listing the minimum threads that run when a Java applet is using audio and images (no less than 16 threads!). Due to its highly concrete and mystery-busting qualities, I found that this appendix would have been better promoted and expanded to fill an entire chapter. The last appendix is barely more than a quick introduction to the thread features of JDB, the most user-unfriendly JDK debugger.

After the fact

Like all Java books, this book suffers from not being able to keep up with Java API developments. Although this first edition is dated 1997, it explicitly limits itself to dealing with JDK 1.0.2. This is unfortunate because method

Thread.interrupt()

(and the associated

InterruptedException

thrown by methods such as

sleep()

) is now fully implemented in JDK 1.1. Had the

interrupt()

method been available to the authors when they set about writing this book, it surely would have impacted their manuscript in many places โ€”

interrupt()

, in combination with

sleep()

, can replace the much more complex

wait()

/

notify()

duo that has to be used in conjunction with Javaโ€™s

synchronized

keyword.

sleep()

and

interrupt()

are free of this requirement and are therefore more flexible (not to mention a whole lot easier to understand).

The authors are obviously quite fond of footnotes because the entire book is littered with them. What bothered me about the footnotes was not their frequency (although Iโ€™ll cop to a bit of annoyance on this front), but that the asterisks, daggers, and double-daggers scattered throughout the text referred to more than peripheral facts or anecdotes: the vast majority of footnotes were actually core text material and should have been skillfully incorporated in the main text. Because the footnotes contain important information, you are constantly switching between the main text and those โ€œfootnotesโ€ that shouldnโ€™t be.

The book also exhausted its quota of permissible errors. I noted several editing mistakes โ€” some minor, some not-so-minor. Far worse than any editing mistakes are factual errors: Page 54 claims that Java does not guarantee the assignment operator be atomic for object (reference) types. This is obviously incorrect as it would become almost unbearable to have to enclose any single assignment operations in a synchronized block (more importantly, the official Java language spec confirms the error!). Given this โ€œoversight,โ€ it is no surprise that the book also omitted the fact that assignment of longs or doubles (the only 64-bit types in Java) really is not guaranteed to be atomic and that these (most exceptionally) have to be protected using a suitable locking mechanism.

The code examples also contained some minor annoyances like the frequent use of old-fashioned, C-style one- or two-letter variable identifiers (something that I find totally irresponsible in a modern text book). One code segment, for example, used โ€œtโ€ as a formal parameter name in two consecutive methods for totally different parameters (an int time and a thread). I also noted instances of variable identifiers that ignore the Java convention of starting a variable with a lowercase character.

Arguably a lot more important is the way the majority of examples seem to ignore exception handling: the authors mostly used empty catch clauses, but some examples used the highly questionable technique of catching Exception itself instead of the specific exception type(s) thrown by any method(s) inside the try block. (Catching Exception in this way stops bug-detecting exceptions like NullPointerException from being caught where they occur โ€” a serious problem when the technique is used in large programs).

Despite the few points of criticism I have for Java Threads, I am glad that such a book was written. The topic of Java threads is often treated as an aside in other books while, like the authors of Java Threads correctly point out, multithreading in Java is really fundamental to every non-trivial Java program. I would like to see a second edition that reflects the latest JDK release, but if you canโ€™t wait until that currently unspecified time, you will get value for money by buying a copy of Java Threads now.

Laurence Vanhelsuwรฉ is an independent software engineer. Self-taught, he dropped out of college and immediately started a professional career writing arcade games. He has worked on such diverse technologies as X.25 WAN routers, virtual reality flight simulation, Postscript, and real-time digitized video-based traffic analysis. Laurence thinks Java will revolutionize computer science by leveling the computing landscape into one pan-Java playing field.