Cameron Laird revisits the practice and concepts of multithreaded programming in Java, this time focusing on more intermediate programming solutions for today's distributed computing problems. Build on what you know about the java.util.concurrent package while learning techniques to improve inter-thread communication and avoid Java concurrency pitfalls.

Multithreaded programming in Java has a reputation for difficulty, but most developers can untangle it with smart, designed-for-concurrency constructs that are standard with the Java platform. In this follow-up to my survey of basic modern threading techniques, I'll introduce some of the constructs in Doug Lea's java.util.concurrent package and also discuss a few standbys of Java threading horror -- which aren't actually such a big deal when properly worked around. All in all, I will touch on seven topics that can help you make the best, or the worst, of your multithreaded programs:

Thread management (a recurring theme)

Programming with Java threads isn't really so hard -- it's thread management that keeps most software developers up at night. Consider the analogy of painting a room: more of your time will be spent on preparation than execution -- choosing and matching colors, clearing and taping the room, and so on. This is because today's paints and brushes make the painting part about as simple and "goofproof" as can be. Setup, as it turns out, is more than two-thirds of the game.

Thread programming works similarly: Using threads is generally much easier than managing (or cultivating) them over the long term. Thread management will be a recurring theme in your study of thread programming, so you might as well start thinking about it now.

For instance, in my previous article I introduced thread management as a simple evaluation of new ExampleThread(). That thread was intended to be destroyed at the end of scope, which is fine for a simple program. But now we're ready to dig into some more sophisticated schemes. In the next sections, look for programs that do some of the following:

Delete or re-use Thread instances

Manage different varieties of a Thread

Require introspection on Thread characteristics such as memory use or life history

Runnable vs Callable

In my last article I introduced a MonitorModel based on a Runnable rather than a Thread. Runnable's more flexible inheritance model gives it the advantage over Thread. On the other hand, both Runnable and Thread share certain limits: neither returns values or throws Exceptions.

For even more capable thread programming, go beyond both Thread and Runnable to use Callable. Callable communicates better than its two friends, because Callable returns results.

Callable is part of the java.util.concurrent package, which first appeared in the Java 5 end-of-summer 2004 release. The program in Listing 1 illustrates both the flexibility and the complications of using Callable:

Some things to note about the program: First, the two players operate independently. Each waits a randomized time, from zero to two seconds, then chooses among Rock, Scissors, or Paper. Also note that the choices take place in separate threads, in indeterminate sequence. For example, in the tenth "hand" Player1 takes four times as long to choose, so Player2's choice appears on stdout first:

Communication to and from each thread is fully programmable using the call() and get() methods. Invocation of the Callable completes immediately. Afterward, when the result of the calculation is ready, it appears through the Future mechanism. And finally, the ExecutorService assumes responsibility for assigning Callables to available Threads for execution. (More about that later.)

While using Callable involves more plumbing than using Runnable, it also makes for cleaner communication. Callable is generally a better choice than Runnable for use cases where computing threads need to exchange data with their invoking process. Runnable also might play rock-scissors-paper, but it would need a way to return the selection of Rock, Scissors, or Paper. An individual programmer would be hard-pressed to code such a communication more elegantly than Callable already does.

ExecutorService vs ForkJoinPool

ExecutorService was introduced in the java.util.concurrent package to help manage progress-tracking and termination for asynchronous tasks. Learn about ExecutorService (and its modernized sidekick, ForkJoinPool) in the Java tip, " When to use ExecutorService vs ForkJoinPool."

Shared resources and immutability

Multithreaded programming makes it much harder to reason about or understand code segments locally. That's because many resources have the potential to be shared between threads, so what happens in one code segment might depend on a distant source, executing in a different thread. The example in Listing 3 illustrates my point.

Listing 3. Example thread-hazardous code segment

If you're disturbed by what you see in Listing 3 then you are not alone! In a multithreaded context, common.balance might have a different value when tested than when it was assigned. While the two statements are consecutive in source code, during execution other source code in a different thread could intervene and update the common.balance value.

Worse, from a programmer's standpoint that sequence of execution isn't deterministic: it might vary from one run to the next.

An effective response to such difficulties is to program with immutable objects. For reasons that go beyond their use in threads, Joshua Bloch famously recommends that developers use immutable classes "unless there's a very good reason to make them mutable." For cases where a class cannot be immutable, he proposes that we limit the mutability "as much as possible" (see Effective Java in Resources).

Using immutable objects ensures thread safety. You can also attain thread safety by doing calculations on mutable objects whose only reference is within the local scope: if a thread can be guaranteed to have the only references to a resource, then using that resource is safe even if it's mutable.

Synchronized blocks

Sometimes a calculation requires mutability, with references that can't be confined to a single thread; what to do then? This situation demands synchronization, which is a kind of locking that guarantees exclusive access by a thread to a shared resource.

Syntactically, the synchronized keyword can be applied to both methods and blocks. In broad terms, block synchronization is more useful. For instance, using block synchronization would transform the code sample from Listing 3 to the following:

Listing 4. Block synchronization enforces thread safety

The synchronized keyword locks the source code segment so that only one thread can execute at a time. You are thus guaranteed that common.balance will have the same value on reading within the thread as when it was written.

Alternately, you could use a slightly different syntax to lock resources for the span of an entire method:

...
public static synchronized int getBalance() {
...

Synchronized locking guarantees that computation of getBalance() is atomic or transactional across its resources.

Synchronization is a relatively delicate matter: It applies only to blocks and methods, not variables. If mis-used it can result in pathologies like deadlock. Synchronization applies only to final fields, and it's managed by methods like wait() and notify(). You can also configure synchronization with java.util.concurrent.locks to yield interruptible or re-entrant locks, which I discuss in the next section. (Also see Resources.)