Chapter 10. Threading

Minor Premise: One man can dig a posthole in sixty seconds.

Conclusion: Sixty men can dig a posthole in one second.

Ambrose Bierce, The Devil's Dictionary

Multithreading allows an application to do multiple things at the same time. While it is often possible to get the same effect with clever programming in a single thread, Java's extensive support of threads makes it easier to use multiple threads. In addition, single-threaded applications cannot take advantage of multiprocessor machines.

However, multithreading can be difficult to implement effectively. Multithreading improves performance in many cases, but it also has drawbacks if the default mechanisms for cooperation between threads are used simplistically. In this chapter, we look at the benefits and the disadvantages threads offer to performance. We examine the likely problems that may be encountered and discuss how to minimize the performance downside while still gaining the benefits of multiple threads.

Synchronization and Monitors

Synchronization can be confusing, so I felt it was worth including a short reminder of its subtleties here.

Two or more threads accessing and updating the same data variables have no way of knowing when a particular accessor update will occur relative to any other thread accesses. Synchronization ensures that a group of statements (a synchronized block) will execute atomically as far as all synchronized threads are concerned. Synchronization does not address the problem of which thread executes the statements first: it is first come, first served.

Synchronization is achieved using monitors. Every object can have a monitor associated with it, so any object can synchronize blocks. Before a synchronized block can be entered, a thread needs to gain ownership of the monitor for that block. Once the thread has gained ownership of the monitor, no other thread synchronized on the same monitor can gain entry to that block (or any other block or method synchronized on the same monitor). The thread owning the monitor gets to execute all the statements in the block, and then automatically releases ownership of the monitor on exiting the block. At that point, another thread waiting to enter the block can acquire ownership of the monitor.

Note, however, that threads synchronized on different monitors can gain entry to the same block at any time. For example, a block defined with a synchronized(this) expression is synchronized on the monitor of the this object. If this is an object that is different for two different threads, both threads can gain ownership of their own monitor for that block, and both can execute the block at the same time. This won't matter if the block affects only variables specific to its thread (such as instance variables of this), but can lead to corrupt states if the block alters variables that are shared between the threads, such as static variables.

Multithreading needs more care in coding than single threading. When tuning threads, it is easy to make a little change here, and a little change there, and end up with total confusion, race conditions, and deadlock. Before you start tuning threads, it is important to have a good understanding of how they interact and how to make them cooperate and control each other. This book is not a tutorial on threads, so I don't intend to cover the subject from a non-performance-tuning standpoint in any great detail. Two excellent books on Java threads are Java Threads by Scott Oaks and Henry Wong (O'Reilly) and Concurrent Programming in Java by Doug Lea (Addison Wesley).

If you are not comfortable with Java synchronization and how it works, I strongly advise you to spend some time studying how to use threads and synchronization. Be sure you understand how race conditions and deadlocks occur (many articles and books on Java go into this in detail, and there are brief examples in the later sections of this chapter). Be sure you know how to correctly use the various wait( ) and notify( ) methods in the Object class as well as the synchronized keyword, and understand which monitor objects are used and how they are used when execution reaches a synchronized block or method.