Chapter 14. Multithreading

WHAT ARE THREADS

INTERRUPTING THREADS

THREAD STATES

THREAD PROPERTIES

SYNCHRONIZATION

BLOCKING QUEUES

THREAD-SAFE COLLECTIONS

CALLABLES AND FUTURES

EXECUTORS

SYNCHRONIZERS

THREADS AND SWING

You are probably familiar with multitasking in your operating system: the ability to have more than one program working at what seems like the same time. For example, you can print while editing or downloading your email. Nowadays, you are likely to have a computer with more than one CPU, but the number of concurrently executing processes is not limited by the number of CPUs. The operating system assigns CPU time slices to each process, giving the impression of parallel activity.

Multithreaded programs extend the idea of multitasking by taking it one level lower: individual programs will appear to do multiple tasks at the same time. Each task is usually called a thread—which is short for thread of control. Programs that can run more than one thread at once are said to be multithreaded.

So, what is the difference between multiple processes and multiple threads? The essential difference is that while each process has a complete set of its own variables, threads share the same data. This sounds somewhat risky, and indeed it can be, as you will see later in this chapter. However, shared variables make communication between threads more efficient and easier to program than interprocess communication. Moreover, on some operating systems, threads are more “lightweight” than processes—it takes less overhead to create and destroy individual threads than it does to launch new processes.

Multithreading is extremely useful in practice. For example, a browser should be able to simultaneously download multiple images. A web server needs to be able to serve concurrent requests. Graphical user interface (GUI) programs have a separate thread for gathering user interface events from the host operating environment. This chapter shows you how to add multithreading capability to your Java applications.

Multithreading changed dramatically in Java SE 5.0, with the addition of a large number of classes and interfaces that provide high-quality implementations of the mechanisms that most application programmers will need. In this chapter, we explain the features that were added to Java SE 5.0 as well as the classic synchronization mechanisms, and help you choose between them.

Fair warning: multithreading can get very complex. In this chapter, we cover all the tools that an application programmer is likely to need. However, for more intricate system-level programming, we suggest that you turn to a more advanced reference, such as Java Concurrency in Practice by Brian Goetz (Addison-Wesley Professional, 2006).

What Are Threads?

Let us start by looking at a program that does not use multiple threads and that, as a consequence, makes it difficult for the user to perform several tasks with that program. After we dissect it, we then show you how easy it is to have this program run separate threads. This program animates a bouncing ball by continually moving the ball, finding out if it bounces against a wall, and then redrawing it. (See Figure 14–1.)

Figure 14–1. Using a thread to animate a bouncing ball

Image

As soon as you click the Start button, the program launches a ball from the upper-left corner of the screen and the ball begins bouncing. The handler of the Start button calls the addBall method. That method contains a loop running through 1,000 moves. Each call to move moves the ball by a small amount, adjusts the direction if it bounces against a wall, and then redraws the panel.

Ball ball = new Ball();
panel.add(ball);

Image

The static sleep method of the Thread class pauses for the given number of milliseconds.

The call to Thread.sleep does not create a new thread—sleep is a static method of the Thread class that temporarily stops the activity of the current thread.

The sleep method can throw an InterruptedException. We discuss this exception and its proper handling later. For now, we simply terminate the bouncing if this exception occurs.

If you run the program, the ball bounces around nicely, but it completely takes over the application. If you become tired of the bouncing ball before it has finished its 1,000 moves and click the Close button, the ball continues bouncing anyway. You cannot interact with the program until the ball has finished bouncing.


Image Note

If you carefully look over the code at the end of this section, you will notice the call

comp.paint(comp.getGraphics())

inside the addBall method of the BounceFrame class. That is pretty strange—normally, you’d call repaint and let the AWT worry about getting the graphics context and doing the painting. But if you try to call comp.repaint() in this program, you’ll find that the panel is only repainted after the addBall method has returned. Also note that the ball component extends JPanel; this makes it easier to erase the background. In the next program, in which we use a separate thread to compute the ball position, we can go back to the familiar use of repaint and JComponent.


Obviously, the behavior of this program is rather poor. You would not want the programs that you use behaving in this way when you ask them to do a time-consuming job. After all, when you are reading data over a network connection, it is all too common to be stuck in a task that you would really like to interrupt. For example, suppose you download a large image and decide, after seeing a piece of it, that you do not need or want to see the rest; you certainly would like to be able to click a Stop or Back button to interrupt the loading process. In the next section, we show you how to keep the user in control by running crucial parts of the code in a separate thread.

Listings 14–1 through 14–3 show the code for the program.

Listing 14–1. Bounce.java

Image

Image

Image

Listing 14–2. Ball.java

Image

Image

Listing 14–3. BallComponent.java

Image

Image

java.lang.Thread 1.0

Image

static void sleep(long millis)

sleeps for the given number of milliseconds.

Image

Using Threads to Give Other Tasks a Chance

We will make our bouncing-ball program more responsive by running the code that moves the ball in a separate thread. In fact, you will be able to launch multiple balls. Each of them is moved by its own thread. In addition, the AWT event dispatch thread continues running in parallel, taking care of user interface events. Because each thread gets a chance to run, the event dispatch thread has the opportunity to notice when a user clicks the Close button while the balls are bouncing. The thread can then process the “close” action.

We use ball-bouncing code as an example to give you a visual impression of the need for concurrency. In general, you will always want to be wary of any long-running computation. Your computation is likely to be a part of some bigger framework, such as a GUI or web framework. Whenever the framework calls one of your methods, there is usually an expectation of a quick return. If you need to do any task that takes a long time, you should use a separate thread.

Here is a simple procedure for running a task in a separate thread:

1. Place the code for the task into the run method of a class that implements the Runnable interface. That interface is very simple, with a single method:

Image

You simply implement a class, like this:

Image

2. Construct an object of your class:

Runnable r = new MyRunnable();

3. Construct a Thread object from the Runnable:

Thread t = new Thread(r);

4. Start the thread:

t.start();

To make our bouncing-ball program into a separate thread, we need only implement a class BallRunnable and place the code for the animation inside the run method, as in the following code:

Image

Again, we need to catch an InterruptedException that the sleep method threatens to throw. We discuss this exception in the next section. Typically, interruption is used to request that a thread terminates. Accordingly, our run method exits when an InterruptedException occurs.

Whenever the Start button is clicked, the addBall method launches a new thread (see Figure 14–2):

Ball b = new Ball();
panel.add(b);
Runnable r = new BallRunnable(b, panel);
Thread t = new Thread(r);
t.start();

Figure 14–2. Running multiple threads

Image

That’s all there is to it! You now know how to run tasks in parallel. The remainder of this chapter tells you how to control the interaction between threads.

The complete code is shown in Listing 14–4.


Image Note

You can also define a thread by forming a subclass of the Thread class, like this:

Image

Then you construct an object of the subclass and call its start method. However, this approach is no longer recommended. You should decouple the task that is to be run in parallel from the mechanism of running it. If you have many tasks, it is too expensive to create a separate thread for each one of them. Instead, you can use a thread pool—see “Executors” on page 778.


Image Caution

Do not call the run method of the Thread class or the Runnable object. Calling the run method directly merely executes the task in the same thread—no new thread is started. Instead, call the Thread.start method. It will create a new thread that executes the run method.


Listing 14–4. BounceThread.java

Image

Image

Image

Image

Image

java.lang.Thread 1.0

Image

Thread(Runnable target)

constructs a new thread that calls the run() method of the specified target.

void start()

starts this thread, causing the run() method to be called. This method will return immediately. The new thread runs concurrently.

void run()

calls the run method of the associated Runnable.

java.lang.Runnable 1.0

Image

void run()

must be overriden and supplied with instructions for the task that you want to have executed.

Interrupting Threads

A thread terminates when its run method returns, by executing a return statement, after executing the last statement in the method body, or if an exception occurs that is not caught in the method. In the initial release of Java, there also was a stop method that another thread could call to terminate a thread. However, that method is now deprecated. We discuss the reason in the section “Why the stop and suspend Methods Are Deprecated” on page 762.

There is a way to force a thread to terminate. However, the interrupt method can be used to request termination of a thread.

When the interrupt method is called on a thread, the interrupted status of the thread is set. This is a boolean flag that is present in every thread. Each thread should occasionally check whether it has been interrupted.

To find out whether the interrupted status was set, first call the static Thread.currentThread method to get the current thread and then call the isInterrupted method:

Image

However, if a thread is blocked, it cannot check the interrupted status. This is where the InterruptedException comes in. When the interrupt method is called on a thread that blocks on a call such as sleep or wait, the blocking call is terminated by an InterruptedException. (There are blocking I/O calls that cannot be interrupted; you should consider interruptible alternatives. See Chapters 1 and 3 of Volume II for details.)

There is no language requirement that a thread that is interrupted should terminate. Interrupting a thread simply grabs its attention. The interrupted thread can decide how to react to the interruption. Some threads are so important that they should handle the exception and continue. But quite commonly, a thread will simply want to interpret an interruption as a request for termination. The run method of such a thread has the following form:

Image

Image

The isInterrupted check is neither necessary nor useful if you call the sleep method (or another interruptible method) after every work iteration. If you call the sleep method when the interrupted status is set, it doesn’t sleep. Instead, it clears the status (!) and throws an InterruptedException. Therefore, if your loop calls sleep, don’t check the interrupted status. Instead, catch the InterruptedException, like this:

Image

Image Note

There are two very similar methods, interrupted and isInterrupted. The interrupted method is a static method that checks whether the current thread has been interrupted. Furthermore, calling the interrupted method clears the interrupted status of the thread. On the other hand, the isInterrupted method is an instance method that you can use to check whether any thread has been interrupted. Calling it does not change the interrupted status.


You’ll find lots of published code in which the InterruptedException is squelched at a low level, like this:

Image

Don’t do that! If you can’t think of anything good to do in the catch clause, you still have two reasonable choices:

In the catch clause, call Thread.currentThread().interrupt() to set the interrupted status. Then the caller can test it.

Image

• Or, even better, tag your method with throws InterruptedException and drop the try block. Then the caller (or, ultimately, the run method) can catch it.

Image

java.lang.Thread 1.0

Image

void interrupt()

sends an interrupt request to a thread. The interrupted status of the thread is set to true. If the thread is currently blocked by a call to sleep, then an InterruptedException is thrown.

static boolean interrupted()

tests whether the current thread (that is, the thread that is executing this instruction) has been interrupted. Note that this is a static method. The call has a side effect—it resets the interrupted status of the current thread to false.

boolean isInterrupted()

tests whether a thread has been interrupted. Unlike the static interrupted method, this call does not change the interrupted status of the thread.

static Thread currentThread()

returns the Thread object representing the currently executing thread.

Thread States

Threads can be in one of six states:

• New

• Runnable

• Blocked

• Waiting

• Timed waiting

• Terminated

Each of these states is explained in the sections that follow.

To determine the current state of a thread, simply call the getState method.

New Threads

When you create a thread with the new operator—for example, new Thread(r)—the thread is not yet running. This means that it is in the new state. When a thread is in the new state, the program has not started executing code inside of it. A certain amount of bookkeeping needs to be done before a thread can run.

Runnable Threads

Once you invoke the start method, the thread is in the runnable state. A runnable thread may or may not actually be running. It is up to the operating system to give the thread time to run. (The Java specification does not call this a separate state, though. A running thread is still in the runnable state.)

Once a thread is running, it doesn’t necessarily keep running. In fact, it is desirable if running threads occasionally pause so that other threads have a chance to run. The details of thread scheduling depend on the services that the operating system provides. Preemptive scheduling systems give each runnable thread a slice of time to perform its task. When that slice of time is exhausted, the operating system preempts the thread and gives another thread an opportunity to work (see Figure 14–4 on page 741). When selecting the next thread, the operating system takes into account the thread priorities—see “Thread Priorities” on page 733 for more information.

All modern desktop and server operating systems use preemptive scheduling. However, small devices such as cell phones may use cooperative scheduling. In such a device, a thread loses control only when it calls the yield method, or it is blocked or waiting.

On a machine with multiple processors, each processor can run a thread, and you can have multiple threads run in parallel. Of course, if there are more threads than processors, the scheduler still has to do time-slicing.

Always keep in mind that a runnable thread may or may not be running at any given time. (This is why the state is called “runnable” and not “running.”)

Blocked and Waiting Threads

When a thread is blocked or waiting, it is temporarily inactive. It doesn’t execute any code and it consumes minimal resources. It is up to the thread scheduler to reactivate it. The details depend on how the inactive state was reached.

• When the thread tries to acquire an intrinsic object lock (but not a Lock in the java.util.concurrent library) that is currently held by another thread, it becomes blocked. (We discuss java.util.concurrent locks in the section “Lock Objects” on page 742 and intrinsic object locks in the section “The synchronized Keyword” on page 750.) The thread becomes unblocked when all other threads have relinquished the lock and the thread scheduler has allowed this thread to hold it.

• When the thread waits for another thread to notify the scheduler of a condition, it enters the waiting state. We discuss conditions in the “Condition Objects” section beginning on page 745. This happens by calling the Object.wait or Thread.join method, or by waiting for a Lock or Condition in the java.util.concurrent library. In practice, the difference between the blocked and waiting state is not significant.

• Several methods have a timeout parameter. Calling them causes the thread to enter the timed waiting state. This state persists either until the timeout expired or the appropriate notification has been received. Methods with timeout include Thread.sleep and the timed versions of Object.wait, Thread.join, Lock.tryLock, and Condition.await.

Figure 14–3 shows the states that a thread can have and the possible transitions from one state to another. When a thread is blocked or waiting (or, of course, when it terminates), another thread will be scheduled to run. When a thread is reactivated (for example, because its timeout has expired or it has succeeded in acquiring a lock), the scheduler checks to see if it has a higher priority than the currently running threads. If so, it preempts one of the current threads and picks a new thread to run.

Figure 14–3. Thread states

Image

Terminated Threads

A thread is terminated for one of two reasons:

• It dies a natural death because the run method exits normally.

• It dies abruptly because an uncaught exception terminates the run method.

In particular, you can kill a thread by invoking its stop method. That method throws a ThreadDeath error object that kills the thread. However, the stop method is deprecated, and you should never call it in your own code.

java.lang.Thread 1.0

Image

void join()

waits for the specified thread to terminate.

void join(long millis)

waits for the specified thread to die or for the specified number of milliseconds to pass.

Thread.State getState() 5.0

gets the state of this thread; one of NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, or TERMINATED.

void stop()

stops the thread. This method is deprecated.

void suspend()

suspends this thread’s execution. This method is deprecated.

void resume()

resumes this thread. This method is only valid after suspend() has been invoked. This method is deprecated.

Thread Properties

In the following sections, we discuss miscellaneous properties of threads: thread priorities, daemon threads, thread groups, and handlers for uncaught exceptions.

Thread Priorities

In the Java programming language, every thread has a priority. By default, a thread inherits the priority of the thread that constructed it. You can increase or decrease the priority of any thread with the setPriority method. You can set the priority to any value between MIN_PRIORITY (defined as 1 in the Thread class) and MAX_PRIORITY (defined as 10). NORM_PRIORITY is defined as 5.

Whenever the thread-scheduler has a chance to pick a new thread, it prefers threads with higher priority. However, thread priorities are highly system dependent. When the virtual machine relies on the thread implementation of the host platform, the Java thread priorities are mapped to the priority levels of the host platform, which may have more or fewer thread priority levels.

For example, Windows has seven priority levels. Some of the Java priorities will map to the same operating system level. In the Sun JVM for Linux, thread priorities are ignored altogether—all threads have the same priority.

Beginning programmers sometimes overuse thread priorities. There are few reasons ever to tweak priorites. You should certainly never structure your programs so that their correct functioning depends on priority levels.


Image Caution

If you do use priorities, you should be aware of a common beginner’s error. If you have several threads with a high priority that don’t become inactive, the lower-priority threads may never execute. Whenever the scheduler decides to run a new thread, it will choose among the highest-priority threads first, even though that may starve the lower-priority threads completely.


java.lang.Thread 1.0

Image

void setPriority(int newPriority)

sets the priority of this thread. The priority must be between Thread.MIN_PRIORITY and Thread.MAX_PRIORITY. Use Thread.NORM_PRIORITY for normal priority.

static int MIN_PRIORITY

is the minimum priority that a Thread can have. The minimum priority value is 1.

static int NORM_PRIORITY

is the default priority of a Thread. The default priority is 5.

static int MAX_PRIORITY

is the maximum priority that a Thread can have. The maximum priority value is 10.

static void yield()

causes the currently executing thread to yield. If there are other runnable threads with a priority at least as high as the priority of this thread, they will be scheduled next. Note that this is a static method.

Daemon Threads

You can turn a thread into a daemon thread by calling

t.setDaemon(true);

There is nothing demonic about such a thread. A daemon is simply a thread that has no other role in life than to serve others. Examples are timer threads that send regular “timer ticks” to other threads or threads that clean up stale cache entries. When only daemon threads remain, the virtual machine exits. There is no point in keeping the program running if all remaining threads are daemons.

Daemon threads are sometimes mistakenly used by beginners who don’t want to think about shutdown actions. However, this can be dangerous. A daemon thread should never access a persistent resource such as a file or database since it can terminate at any time, even in the middle of an operation.

java.lang.Thread 1.0

Image

void setDaemon(boolean isDaemon)

marks this thread as a daemon thread or a user thread. This method must be called before the thread is started.

Handlers for Uncaught Exceptions

The run method of a thread cannot throw any checked exceptions, but it can be terminated by an unchecked exception. In that case, the thread dies.

However, there is no catch clause to which the exception can be propagated. Instead, just before the thread dies, the exception is passed to a handler for uncaught exceptions.

The handler must belong to a class that implements the Thread.UncaughtExceptionHandler interface. That interface has a single method,

void uncaughtException(Thread t, Throwable e)

As of Java SE 5.0, you can install a handler into any thread with the setUncaughtExceptionHandler method. You can also install a default handler for all threads with the static method setDefaultUncaughtExceptionHandler of the Thread class. A replacement handler might use the logging API to send reports of uncaught exceptions into a log file.

If you don’t install a default handler, the default handler is null. However, if you don’t install a handler for an individual thread, the handler is the thread’s ThreadGroup object.


Image Note

A thread group is a collection of threads that can be managed together. By default, all threads that you create belong to the same thread group, but it is possible to establish other groupings. Since Java SE 5.0 introduced better features for operating on collections of threads, you should not use thread groups in your own programs.


The ThreadGroup class implements the Thread.UncaughtExceptionHandler interface. Its uncaughtException method takes the following action:

1. If the thread group has a parent, then the uncaughtException method of the parent group is called.

2. Otherwise, if the Thread.getDefaultExceptionHandler method returns a non-null handler, it is called.

3. Otherwise, if the Throwable is an instance of ThreadDeath, nothing happens.

4. Otherwise, the name of the thread and the stack trace of the Throwable are printed on System.err.

That is the stack trace that you have undoubtedly seen many times in your programs.

java.lang.Thread 1.0

Image

static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) 5.0

static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() 5.0

sets or gets the default handler for uncaught exceptions.

void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) 5.0

Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() 5.0

sets or gets the handler for uncaught exceptions. If no handler is installed, the thread group object is the handler.


java.lang.Thread.UncaughtExceptionHandler 5.0

Image

void uncaughtException(Thread t, Throwable e)

defined to log a custom report when a thread is terminated with an uncaught exception.

Image

java.lang.ThreadGroup 1.0

Image

void uncaughtException(Thread t, Throwable e)

calls this method of the parent thread group if there is a parent, or calls the default handler of the Thread class if there is a default handler, or otherwise prints a stack trace to the standard error stream. (However, if e is a ThreadDeath object, the stack trace is suppressed. ThreadDeath objects are generated by the deprecated stop method.)

Synchronization

In most practical multithreaded applications, two or more threads need to share access to the same data. What happens if two threads have access to the same object and each calls a method that modifies the state of the object? As you might imagine, the threads can step on each other’s toes. Depending on the order in which the data were accessed, corrupted objects can result. Such a situation is often called a race condition.

An Example of a Race Condition

To avoid corruption of shared data by multiple threads, you must learn how to synchronize the access. In this section, you’ll see what happens if you do not use synchronization. In the next section, you’ll see how to synchronize data access.

In the next test program, we simulate a bank with a number of accounts. We randomly generate transactions that move money between these accounts. Each account has one thread. Each transaction moves a random amount of money from the account serviced by the thread to another random account.

The simulation code is straightforward. We have the class Bank with the method transfer. This method transfers some amount of money from one account to another. (We don’t yet worry about negative account balances.) Here is the code for the transfer method of the Bank class.

Image

Here is the code for the TransferRunnable class. Its run method keeps moving money out of a fixed bank account. In each iteration, the run method picks a random target account and a random amount, calls transfer on the bank object, and then sleeps.

Image

When this simulation runs, we do not know how much money is in any one bank account at any time. But we do know that the total amount of money in all the accounts should remain unchanged because all we do is move money from one account to another.

At the end of each transaction, the transfer method recomputes the total and prints it.

This program never finishes. Just press CTRL+C to kill the program.

Here is a typical printout:

Image

As you can see, something is very wrong. For a few transactions, the bank balance remains at $100,000, which is the correct total for 100 accounts of $1,000 each. But after some time, the balance changes slightly. When you run this program, you may find that errors happen quickly or it may take a very long time for the balance to become corrupted. This situation does not inspire confidence, and you would probably not want to deposit your hard-earned money in this bank.

The program in Listings 14–5 through 14–7 provides the complete source code. See if you can spot the problems with the code. We will unravel the mystery in the next section.

Listing 14–5. UnsynchBankTest.java

Image

Listing 14–6. Bank.java

Image

Image

Listing 14–7. TransferRunnable.java

Image

The Race Condition Explained

In the previous section, we ran a program in which several threads updated bank account balances. After a while, errors crept in and some amount of money was either lost or spontaneously created. This problem occurs when two threads are simultaneously trying to update an account. Suppose two threads simultaneously carry out the instruction

accounts[to] += amount;

The problem is that these are not atomic operations. The instruction might be processed as follows:

1. Load accounts[to] into a register.

2. Add amount.

3. Move the result back to accounts[to].

Now, suppose the first thread executes Steps 1 and 2, and then it is preempted. Suppose the second thread awakens and updates the same entry in the account array. Then, the first thread awakens and completes its Step 3.

That action wipes out the modification of the other thread. As a result, the total is no longer correct. (See Figure 14–4.)

Figure 14–4. Simultaneous access by two threads

Image

Our test program detects this corruption. (Of course, there is a slight chance of false alarms if the thread is interrupted as it is performing the tests!)


Image Note

You can actually peek at the virtual machine bytecodes that execute each statement in our class. Run the command

javap -c -v Bank

to decompile the Bank.class file. For example, the line

accounts[to] += amount;

is translated into the following bytecodes:

Image

What these codes mean does not matter. The point is that the increment command is made up of several instructions, and the thread executing them can be interrupted at the point of any instruction.


What is the chance of this corruption occurring? We boosted the chance of observing the problem by interleaving the print statements with the statements that update the balance.

If you omit the print statements, the risk of corruption is quite a bit lower because each thread does so little work before going to sleep again, and it is unlikely that the scheduler will preempt it in the middle of the computation. However, the risk of corruption does not completely go away. If you run lots of threads on a heavily loaded machine, then the program will still fail even after you have eliminated the print statements. The failure may take a few minutes or hours or days to occur. Frankly, there are few things worse in the life of a programmer than an error that only manifests itself once every few days.

The real problem is that the work of the transfer method can be interrupted in the middle. If we could ensure that the method runs to completion before the thread loses control, then the state of the bank account object would never be corrupted.

Lock Objects

Starting with Java SE 5.0, there are two mechanisms for protecting a code block from concurrent access. The Java language provides a synchronized keyword for this purpose, and Java SE 5.0 introduced the ReentrantLock class. The synchronized keyword automatically provides a lock as well as an associated “condition,” which makes it powerful and convenient for most cases that require explicit locking. However, we believe that it is easier to understand the synchronized keyword after you have seen locks and conditions in isolation. The java.util.concurrent framework provides separate classes for these fundamental mechanisms, which we explain here and in the section “Condition Objects” on page 745. Once you have understood these building blocks, we present the section “The synchronized Keyword” on page 750.

The basic outline for protecting a code block with a ReentrantLock is:

Image

This construct guarantees that only one thread at a time can enter the critical section. As soon as one thread locks the lock object, no other thread can get past the lock statement. When other threads call lock, they are deactivated until the first thread unlocks the lock object.


Image Caution

It is critically important that the unlock operation is enclosed in a finally clause. If the code in the critical section throws an exception, the lock must be unlocked. Otherwise, the other threads will be blocked forever.


Let us use a lock to protect the transfer method of the Bank class.

Image

Suppose one thread calls transfer and gets preempted before it is done. Suppose a second thread also calls transfer. The second thread cannot acquire the lock and is blocked in the call to the lock method. It is deactivated and must wait for the first thread to finish executing the transfer method. When the first thread unlocks the lock, then the second thread can proceed (see Figure 14–5).

Figure 14–5. Comparison of unsynchronized and synchronized threads

Image

Try it out. Add the locking code to the transfer method and run the program again. You can run it forever, and the bank balance will not become corrupted.

Note that each Bank object has its own ReentrantLock object. If two threads try to access the same Bank object, then the lock serves to serialize the access. However, if two threads access different Bank objects, then each thread acquires a different lock and neither thread is blocked. This is as it should be, because the threads cannot interfere with another when they manipulate different Bank instances.

The lock is called reentrant because a thread can repeatedly acquire a lock that it already owns. The lock keeps a hold count that keeps track of the nested calls to the lock method. The thread has to call unlock for every call to lock in order to relinquish the lock. Because of this feature, code that is protected by a lock can call another method that uses the same locks.

For example, the transfer method calls the getTotalBalance method, which also locks the bankLock object, which now has a hold count of 2. When the getTotalBalance method exits, the hold count is back to 1. When the transfer method exits, the hold count is 0, and the thread relinquishes the lock.

In general, you will want to protect blocks of code that update or inspect a shared object. You are then assured that these operations run to completion before another thread can use the same object.


Image Caution

You need to be careful that code in a critical section is not bypassed through the throwing of an exception. If an exception is thrown before the end of the section, then the finally clause will relinquish the lock but the object may be in a damaged state.


java.util.concurrent.locks.Lock 5.0

Image

void lock()

acquires this lock; blocks if the lock is currently owned by another thread.

void unlock()

releases this lock.

java.util.concurrent.locks.ReentrantLock 5.0

Image

ReentrantLock()

constructs a reentrant lock that can be used to protect a critical section.

ReentrantLock(boolean fair)

constructs a lock with the given fairness policy. A fair lock favors the thread that has been waiting for the longest time. However, this fairness guarantee can be a significant drag on performance. Therefore, by default, locks are not required to be fair.


Image Caution

It sounds nicer to be fair, but fair locks are a lot slower than regular locks. You should only enable fair locking if you truly know what you are doing and have a specific reason why fairness is essential for your problem. Even if you use a fair lock, you have no guarantee that the thread scheduler is fair. If the thread scheduler chooses to neglect a thread that has been waiting a long time for the lock, then it doesn’t get the chance to be treated fairly by the lock.


Condition Objects

Often, a thread enters a critical section, only to discover that it can’t proceed until a condition is fulfilled. You use a condition object to manage threads that have acquired a lock but cannot do useful work. In this section, we introduce the implementation of condition objects in the Java library. (For historical reasons, condition objects are often called condition variables.)

Let us refine our simulation of the bank. We do not want to transfer money out of an account that does not have the funds to cover the transfer. Note that we cannot use code like

Image

It is entirely possible that the current thread will be deactivated between the successful outcome of the test and the call to transfer.

Image

By the time the thread is running again, the account balance may have fallen below the withdrawal amount. You must make sure that no other thread can modify the balance between the test and the transfer action. You do so by protecting both the test and the transfer action with a lock:

Image

Now, what do we do when there is not enough money in the account? We wait until some other thread has added funds. But this thread has just gained exclusive access to the bankLock, so no other thread has a chance to make a deposit. This is where condition objects come in.

A lock object can have one or more associated condition objects. You obtain a condition object with the newCondition method. It is customary to give each condition object a name that evokes the condition that it represents. For example, here we set up a condition object to represent the “sufficient funds” condition.

Image

If the transfer method finds that sufficient funds are not available, it calls

sufficientFunds.await();

The current thread is now deactivated and gives up the lock. This lets in another thread that can, we hope, increase the account balance.

There is an essential difference between a thread that is waiting to acquire a lock and a thread that has called await. Once a thread calls the await method, it enters a wait set for that condition. The thread is not made runnable when the lock is available. Instead, it stays deactivated until another thread has called the signalAll method on the same condition.

When another thread transfers money, then it should call

sufficientFunds.signalAll();

This call reactivates all threads that are waiting for the condition. When the threads are removed from the wait set, they are again runnable and the scheduler will eventually activate them again. At that time, they will attempt to reenter the object. As soon as the lock is available, one of them will acquire the lock and continue where it left off, returning from the call to await.

At this time, the thread should test the condition again. There is no guarantee that the condition is now fulfilled—the signalAll method merely signals to the waiting threads that it may be fulfilled at this time and that it is worth checking for the condition again.


Image Note

In general, a call to await should be inside a loop of the form

Image


It is crucially important that some other thread calls the signalAll method eventually. When a thread calls await, it has no way of reactivating itself. It puts its faith in the other threads. If none of them bother to reactivate the waiting thread, it will never run again. This can lead to unpleasant deadlock situations. If all other threads are blocked and the last active thread calls await without unblocking one of the others, then it also blocks. No thread is left to unblock the others, and the program hangs.

When should you call signalAll? The rule of thumb is to call signalAll whenever the state of an object changes in a way that might be advantageous to waiting threads. For example, whenever an account balance changes, the waiting threads should be given another chance to inspect the balance. In our example, we call signalAll when we have finished the funds transfer.

Image

Note that the call to signalAll does not immediately activate a waiting thread. It only unblocks the waiting threads so that they can compete for entry into the object after the current thread has exited the synchronized method.

Another method, signal, unblocks only a single thread from the wait set, chosen at random. That is more efficient than unblocking all threads, but there is a danger. If the randomly chosen thread finds that it still cannot proceed, then it becomes blocked again. If no other thread calls signal again, then the system deadlocks.


Image Caution

A thread can only call await, signalAll, or signal on a condition when it owns the lock of the condition.


If you run the sample program in Listing 14–8, you will notice that nothing ever goes wrong. The total balance stays at $100,000 forever. No account ever has a negative balance. (Again, you need to press CTRL+C to terminate the program.) You may also notice that the program runs a bit slower—this is the price you pay for the added bookkeeping involved in the synchronization mechanism.

In practice, using conditions correctly can be quite challenging. Before you start implementing your own condition objects, you should consider using one of the constructs described in “Synchronizers” on page 785.

Listing 14–8. Bank.java

Image

Image

Image

Image

java.util.concurrent.locks.Lock 5.0

Image

Condition newCondition()

returns a condition object that is associated with this lock.

java.util.concurrent.locks.Condition 5.0

Image

void await()

puts this thread on the wait set for this condition.

void signalAll()

unblocks all threads in the wait set for this condition.

void signal()

unblocks one randomly selected thread in the wait set for this condition.

The synchronized Keyword

In the preceding sections, you saw how to use Lock and Condition objects. Before going any further, let us summarize the key points about locks and conditions:

• A lock protects sections of code, allowing only one thread to execute the code at a time.

• A lock manages threads that are trying to enter a protected code segment.

• A lock can have one or more associated condition objects.

• Each condition object manages threads that have entered a protected code section but that cannot proceed.

The Lock and Condition interfaces were added to Java SE 5.0 to give programmers a high degree of control over locking. However, in most situations, you don’t need that control, and you can use a mechanism that is built into the Java language. Ever since version 1.0, every object in Java has an intrinsic lock. If a method is declared with the synchronized keyword, then the object’s lock protects the entire method. That is, to call the method, a thread must acquire the intrinsic object lock.

In other words,

Image

is the equivalent of

Image

Image

For example, instead of using an explicit lock, we can simply declare the transfer method of the Bank class as synchronized.

The intrinsic object lock has a single associated condition. The wait method adds a thread to the wait set, and the notifyAll/notify methods unblock waiting threads. In other words, calling wait or notifyAll is the equivalent of

intrinsicCondition.await();
intrinsicCondition.signalAll();


Image Note

The wait, notifyAll, and notify methods are final methods of the Object class. The Condition methods had to be named await, signalAll, and signal so that they don’t conflict with those methods.


For example, you can implement the Bank class in Java like this:

Image

As you can see, using the synchronized keyword yields code that is much more concise. Of course, to understand this code, you have to know that each object has an intrinsic lock, and that the lock has an intrinsic condition. The lock manages the threads that try to enter a synchronized method. The condition manages the threads that have called wait.


Image Tip

Synchronized methods are relatively straightforward. However, beginners often struggle with conditions. Before you use wait/notifyAll, you should consider using one of the constructs described in “Synchronizers” on page 785.


It is also legal to declare static methods as synchronized. If such a method is called, it acquires the intrinsic lock of the associated class object. For example, if the Bank class has a static synchronized method, then the lock of the Bank.class object is locked when it is called. As a result, no other thread can call this or any other synchronized static method of the same class.

The intrinsic locks and conditions have some limitations. Among them:

• You cannot interrupt a thread that is trying to acquire a lock.

• You cannot specify a timeout when trying to acquire a lock.

• Having a single condition per lock can be inefficient.

What should you use in your code—Lock and Condition objects or synchronized methods? Here is our recommendation:

• It is best to use neither Lock/Condition nor the synchronized keyword. In many situations, you can use one of the mechanisms of the java.util.concurrent package that do all the locking for you. For example, in “Blocking Queues” on page 764, you will see how to use a blocking queue to synchronize threads that work on a common task.

• If the synchronized keyword works for your situation, by all means, use it. You write less code and have less room for error. Listing 14–9 shows the bank example, implemented with synchronized methods.

• Use Lock/Condition if you specifically need the additional power that these constructs give you.

Listing 14–9. Bank.java

Image

Image

java.lang.Object 1.0

Image

void notifyAll()

unblocks the threads that called wait on this object. This method can only be called from within a synchronized method or block. The method throws an IllegalMonitorStateException if the current thread is not the owner of the object’s lock.

void notify()

unblocks one randomly selected thread among the threads that called wait on this object. This method can only be called from within a synchronized method or block. The method throws an IllegalMonitorStateException if the current thread is not the owner of the object’s lock.

void wait()

causes a thread to wait until it is notified. This method can only be called from within a synchronized method. It throws an IllegalMonitorStateException if the current thread is not the owner of the object’s lock.

void wait(long millis)

void wait(long millis, int nanos)

causes a thread to wait until it is notified or until the specified amount of time has passed. These methods can only be called from within a synchronized method. They throw an IllegalMonitorStateException if the current thread is not the owner of the object’s lock.

Image

Synchronized Blocks

As we just discussed, every Java object has a lock. A thread can acquire the lock by calling a synchronized method. There is a second mechanism for acquiring the lock, by entering a synchronized block. When a thread enters a block of the form

Image

then it acquires the lock for obj.

You will sometimes find “ad hoc” locks, such as

Image

Here, the lock object is created only to use the lock that every Java object possesses.

Sometimes, programmers use the lock of an object to implement additional atomic operations, a practice known as client-side locking. Consider, for example, the Vector class, a list whose methods are synchronized. Now suppose we stored our bank balances in a Vector<Double>. Here is a naive implementation of a transfer method:

Image

Image

The get and set methods of the Vector class are synchronized, but that doesn’t help us. It is entirely possible for a thread to be preempted in the transfer method after the first call to get has been completed. Another thread may then store a different value into the same position. However, we can hijack the lock:

Image

This approach works, but it is entirely dependent on the fact that the Vector class uses the intrinsic lock for all of its mutator methods. However, is this really a fact? The documentation of the Vector class makes no such promise. You have to carefully study the source code and hope that future versions do not introduce unsynchronized mutators. As you can see, client-side locking is very fragile and not generally recommended.

The Monitor Concept

Locks and conditions are powerful tools for thread synchronization, but they are not very object oriented. For many years, researchers have looked for ways to make multithreading safe without forcing programmers to think about explicit locks. One of the most successful solutions is the monitor concept that was pioneered by Per Brinch Hansen and Tony Hoare in the 1970s. In the terminology of Java, a monitor has these properties:

• A monitor is a class with only private fields.

• Each object of that class has an associated lock.

• All methods are locked by that lock. In other words, if a client calls obj.method(), then the lock for obj is automatically acquired at the beginning of the method call and relinquished when the method returns. Because all fields are private, this arrangement ensures that no thread can access the fields while another thread manipulates them.

• The lock can have any number of associated conditions.

Earlier versions of monitors had a single condition, with a rather elegant syntax. You can simply call await accounts[from] >= balance without using an explicit condition variable. However, research showed that indiscriminate retesting of conditions can be inefficient. This problem is solved with explicit condition variables, each managing a separate set of threads.

The Java designers loosely adapted the monitor concept. Every object in Java has an intrinsic lock and an intrinsic condition. If a method is declared with the synchronized keyword, then it acts like a monitor method. The condition variable is accessed by calling wait/notifyAll/notify.

However, a Java object differs from a monitor in three important ways, compromising thread safety:

• Fields are not required to be private.

• Methods are not required to be synchronized.

• The intrinsic lock is available to clients.

This disrespect for security enraged Per Brinch Hansen. In a scathing review of the multithreading primitives in Java, he wrote: “It is astounding to me that Java’s insecure parallelism is taken seriously by the programming community, a quarter of a century after the invention of monitors and Concurrent Pascal. It has no merit.” [Java’s Insecure Parallelism, ACM SIGPLAN Notices 34:38–45, April 1999.]

Volatile Fields

Sometimes, it seems excessive to pay the cost of synchronization just to read or write an instance field or two. After all, what can go wrong? Unfortunately, with modern processors and compilers, there is plenty of room for error:

• Computers with multiple processors can temporarily hold memory values in registers or local memory caches. As a consequence, threads running in different processors may see different values for the same memory location!

• Compilers can reorder instructions for maximum throughput. Compilers won’t choose an ordering that changes the meaning of the code, but they make the assumption that memory values are only changed when there are explicit instructions in the code. However, a memory value can be changed by another thread!

If you use locks to protect code that can be accessed by multiple threads, then you won’t have these problems. Compilers are required to respect locks by flushing local caches as necessary and not inappropriately reordering instructions. The details are explained in the Java Memory Model and Thread Specification developed by JSR 133 (see http://www.jcp.org/en/jsr/detail?id=133). Much of the specification is highly complex and technical, but the document also contains a number of clearly explained examples. A more accessible overview article by Brian Goetz is available at http://www-106.ibm.com/developerworks/java/library/j-jtp02244.html.


Image Note

Brian Goetz coined the following “synchronization motto”: “If you write a variable which may next be read by another thread, or you read a variable which may have last been written by another thread, you must use synchronization.”


The volatile keyword offers a lock-free mechanism for synchronizing access to an instance field. If you declare a field as volatile, then the compiler and the virtual machine take into account that the field may be concurrently updated by another thread.

For example, suppose an object has a boolean flag done that is set by one thread and queried by another thread. As we already discussed, you can use a lock:

public synchronized boolean isDone() { return done; }
public synchronized void setDone() { done = true; }
private boolean done;

Perhaps it is not a good idea to use the intrinsic object lock. The isDone and setDone methods can block if another thread has locked the object. If that is a concern, one can use a separate Lock just for this variable. But this is getting to be a lot of trouble.

In this case, it is reasonable to declare the field as volatile:

public boolean isDone() { return done; }
public void setDone() { done = true; }
private volatile boolean done;


Image Caution

Volatile variables do not provide any atomicity. For example, the method

public void flipDone() { done = !done; } // not atomic

is not guaranteed to flip the value of the field.


In this very simple case, there is a third possibility, to use an AtomicBoolean. This class has methods get and set that are guaranteed to be atomic (as if they were synchronized). The implementation uses efficient machine-level instructions that guarantee atomicity without using locks. There are a number of wrapper classes in the java.util.concurrent.atomic package for atomic integers, floating point numbers, arrays, and so on. These classes are intended for systems programmers who produce concurrency utilities, not for the application programmer.

In summary, concurrent access to a field is safe in these three conditions:

• The field is final, and it is accessed after the constructor has completed.

• Every access to the field is protected by a common lock.

• The field is volatile.


Image Note

Prior to Java SE 5.0, the semantics of volatile were rather permissive. The language designers attempted to give implementors leeway in optimizing the performance of code that uses volatile fields. However, the old specification was so complex that implementors didn’t always follow it, and it allowed confusing and undesirable behavior, such as immutable objects that weren’t truly immutable.


Deadlocks

Locks and conditions cannot solve all problems that might arise in multithreading. Consider the following situation:

Account 1: $200

Account 2: $300

Thread 1: Transfer $300 from Account 1 to Account 2

Thread 2: Transfer $400 from Account 2 to Account 1

As Figure 14–6 indicates, Threads 1 and 2 are clearly blocked. Neither can proceed because the balances in Accounts 1 and 2 are insufficient.

Figure 14–6. A deadlock situation

Image

Is it possible that all threads are blocked because each is waiting for more money? Such a situation is called a deadlock.

In our program, a deadlock cannot occur for a simple reason. Each transfer amount is for, at most, $1,000. Because there are 100 accounts and a total of $100,000 in them, at least one of the accounts must have more than $1,000 at any time. The thread moving money out of that account can therefore proceed.

But if you change the run method of the threads to remove the $1,000 transaction limit, deadlocks can occur quickly. Try it out. Set NACCOUNTS to 10. Construct each transfer runnable with a max value of 2 * INITIAL_BALANCE and run the program. The program will run for a while and then hang.


Image Tip

When the program hangs, type CTRL+. You will get a thread dump that lists all threads. Each thread has a stack trace, telling you where it is currently blocked. Alternatively, run jconsole, as described in Chapter 11, and consult the Threads panel (see Figure 14–7).


Figure 14–7. The Threads panel in jconsole

Image

Another way to create a deadlock is to make the i’th thread responsible for putting money into the i’th account, rather than for taking it out of the i’th account. In this case, there is a chance that all threads will gang up on one account, each trying to remove more money from it than it contains. Try it out. In the SynchBankTest program, turn to the run method of the TransferRunnable class. In the call to transfer, flip fromAccount and toAccount. Run the program and see how it deadlocks almost immediately.

Here is another situation in which a deadlock can occur easily: Change the signalAll method to signal in the SynchBankTest program. You will find that the program hangs eventually. (Again, it is best to set NACCOUNTS to 10 to observe the effect more quickly.) Unlike signalAll, which notifies all threads that are waiting for added funds, the signal method unblocks only one thread. If that thread can’t proceed, all threads can be blocked. Consider the following sample scenario of a developing deadlock.

Account 1:$1,990

All other accounts:$990 each

Thread 1:Transfer $995 from Account 1 to Account 2

All other threads:Transfer $995 from their account to another account

Clearly, all threads but Thread 1 are blocked, because there isn’t enough money in their accounts.

Thread 1 proceeds. Afterward, we have the following situation:

Account 1: $995

Account 2: $1,985

All other accounts: $990 each

Then, Thread 1 calls signal. The signal method picks a thread at random to unblock. Suppose it picks Thread 3. That thread is awakened, finds that there isn’t enough money in its account, and calls await again. But Thread 1 is still running. A new random transaction is generated, say,

Thread 1: Transfer $997 from Account 1 to Account 2

Now, Thread 1 also calls await, and all threads are blocked. The system has deadlocked.

The culprit here is the call to signal. It only unblocks one thread, and it may not pick the thread that is essential to make progress. (In our scenario, Thread 2 must proceed to take money out of Account 2.)

Unfortunately, there is nothing in the Java programming language to avoid or break these deadlocks. You must design your program to ensure that a deadlock situation cannot occur.

Lock Testing and Timeouts

A thread blocks indefinitely when it calls the lock method to acquire a lock that is owned by another thread. You can be more cautious about acquiring a lock. The tryLock method tries to acquire a lock and returns true if it was successful. Otherwise, it immediately returns false, and the thread can go off and do something else.

Image

You can call tryLock with a timeout parameter, like this:

if (myLock.tryLock(100, TimeUnit.MILLISECONDS)) . . .

TimeUnit is an enumeration with values SECONDS, MILLISECONDS, MICROSECONDS, and NANOSECONDS.

The lock method cannot be interrupted. If a thread is interrupted while it is waiting to acquire a lock, the interrupted thread continues to be blocked until the lock is available. If a deadlock occurs, then the lock method can never terminate.

However, if you call tryLock with a timeout, then an InterruptedException is thrown if the thread is interrupted while it is waiting. This is clearly a useful feature because it allows a program to break up deadlocks.

You can also call the lockInterruptibly method. It has the same meaning as tryLock with an infinite timeout.

When you wait on a condition, you can also supply a timeout:

myCondition.await(100, TimeUnit.MILLISECONDS))

The await method returns if another thread has activated this thread by calling signalAll or signal, or if the timeout has elapsed, or if the thread was interrupted.

The await methods throw an InterruptedException if the waiting thread is interrupted. In the (perhaps unlikely) case that you’d rather continue waiting, use the awaitUninterruptibly method instead.


java.util.concurrent.locks.Lock 5.0

Image

boolean tryLock()

tries to acquire the lock without blocking; returns true if it was successful. This method grabs the lock if it is available even if it has a fair locking policy and other threads have been waiting.

boolean tryLock(long time, TimeUnit unit)

tries to acquire the lock, blocking no longer than the given time; returns true if it was successful.

void lockInterruptibly()

acquires the lock, blocking indefinitely. If the thread is interrupted, throws an InterruptedException.

java.util.concurrent.locks.Condition 5.0

Image

boolean await(long time, TimeUnit unit)

enters the wait set for this condition, blocking until the thread is removed from the wait set or the given time has elapsed. Returns false if the method returned because the time elapsed, true otherwise.

void awaitUninterruptibly()

enters the wait set for this condition, blocking until the thread is removed from the wait set. If the thread is interrupted, this method does not throw an InterruptedException.

Read/Write Locks

The java.util.concurrent.locks package defines two lock classes, the ReentrantLock that we already discussed and the ReentrantReadWriteLock class. The latter is useful when there are many threads that read from a data structure and fewer threads that modify it. In that situation, it makes sense to allow shared access for the readers. Of course, a writer must still have exclusive access.

Here are the steps that are necessary to use read/write locks:

1. Construct a ReentrantReadWriteLock object:

private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

2. Extract read and write locks:

private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();

3. Use the read lock in all accessors:

Image

Image

4. Use the write lock in all mutators:

Image

java.util.concurrent.locks.ReentrantReadWriteLock 5.0

Image

Lock readLock()

gets a read lock that can be acquired by multiple readers, excluding all writers.

Lock writeLock()

gets a write lock that excludes all other readers and writers.

Why the stop and suspend Methods Are Deprecated

The initial release of Java defined a stop method that simply terminates a thread, and a suspend method that blocks a thread until another thread calls resume. The stop and suspend methods have something in common: Both attempt to control the behavior of a given thread without the thread’s cooperation.

Both of these methods have been deprecated since Java SE 1.2. The stop method is inherently unsafe, and experience has shown that the suspend method frequently leads to deadlocks. In this section, you will see why these methods are problematic and what you can do to avoid problems.

Let us turn to the stop method first. This method terminates all pending methods, including the run method. When a thread is stopped, it immediately gives up the locks on all objects that it has locked. This can leave objects in an inconsistent state. For example, suppose a TransferThread is stopped in the middle of moving money from one account to another, after the withdrawal and before the deposit. Now the bank object is damaged. Since the lock has been relinquished, the damage is observable from the other threads that have not been stopped.

When a thread wants to stop another thread, it has no way of knowing when the stop method is safe and when it leads to damaged objects. Therefore, the method has been deprecated. You should interrupt a thread when you want it to stop. The interrupted thread can then stop when it is safe to do so.


Image Note

Some authors claim that the stop method has been deprecated because it can cause objects to be permanently locked by a stopped thread. However, that claim is not valid. A stopped thread exits all synchronized methods it has called—technically, by throwing a ThreadDeath exception. As a consequence, the thread relinquishes the intrinsic object locks that it holds.


Next, let us see what is wrong with the suspend method. Unlike stop, suspend won’t damage objects. However, if you suspend a thread that owns a lock, then the lock is unavailable until the thread is resumed. If the thread that calls the suspend method tries to acquire the same lock, then the program deadlocks: The suspended thread waits to be resumed, and the suspending thread waits for the lock.

This situation occurs frequently in graphical user interfaces. Suppose we have a graphical simulation of our bank. A button labeled Pause suspends the transfer threads, and a button labeled Resume resumes them.

Image

Suppose a paintComponent method paints a chart of each account, calling a getBalances method to get an array of balances.

As you will see in the section “Threads and Swing” on page 794, both the button actions and the repainting occur in the same thread, the event dispatch thread. Consider the following scenario:

1. One of the transfer threads acquires the lock of the bank object.

2. The user clicks the Pause button.

3. All transfer threads are suspended; one of them still holds the lock on the bank object.

4. For some reason, the account chart needs to be repainted.

5. The paintComponent method calls the getBalances method.

6. That method tries to acquire the lock of the bank object.

Now the program is frozen.

The event dispatch thread can’t proceed because the lock is owned by one of the suspended threads. Thus, the user can’t click the Resume button, and the threads won’t ever resume.

If you want to safely suspend a thread, introduce a variable suspendRequested and test it in a safe place of your run method—somewhere your thread doesn’t lock objects that other threads need. When your thread finds that the suspendRequested variable has been set, it should keep waiting until it becomes available again.

The following code framework implements that design:

Image

Image

Blocking Queues

You have now seen the low-level building blocks that form the foundations of concurrent programming in Java. However, for practical programming, you want to stay away from the low-level constructs whenever possible. It is much easier and safer to use higher level structures that have been implemented by concurrency experts.

Many threading problems can be formulated elegantly and safely by using one or more queues. Producer threads insert items into the queue, and consumer threads retrieve them. The queue lets you safely hand over data from one thread to another. For example, consider our bank transfer program. Rather than accessing the bank object directly, the transfer threads insert transfer instruction objects into a queue. Another thread removes the instructions from the queue and carries out the transfers. Only that thread has access to the internals of the bank object. No synchronization is necessary. (Of course, the implementors of the threadsafe queue classes had to worry about locks and conditions, but that was their problem, not yours.)

A blocking queue causes a thread to block when you try to add an element when the queue is currently full or to remove an element when the queue is empty. Blocking queues are a useful tool for coordinating the work of multiple threads. Worker threads can periodically deposit intermediate results in a blocking queue. Other worker threads remove the intermediate results and modify them further. The queue automatically balances the workload. If the first set of threads runs slower than the second, the second set blocks while waiting for the results. If the first set of threads runs faster, the queue fills up until the second set catches up. Table 14–1 shows the methods for blocking queues.

Table 14–1. Blocking Queue Methods

Image

The blocking queue methods fall into three categories, depending on their action when the queue is full or empty. If you use the queue as a thread management tool, you will want to use the put and take methods. The add, remove, and element operations throw an exception when you try to add to a full queue or get the head of an empty queue. Of course, in a multithreaded program, the queue might become full or empty at any time, so you will instead want to use the offer, poll, and peek methods. These methods simply return with a failure indicator instead of throwing an exception if they cannot carry out their tasks.


Image Note

The poll and peek methods return null to indicate failure. Therefore, it is illegal to insert null values into these queues.


There are also variants of the offer and poll methods with a timeout. For example, the call

boolean success = q.offer(x, 100, TimeUnit.MILLISECONDS);

tries for 100 milliseconds to insert an element to the tail of the queue. If it succeeds, it returns true; otherwise, it returns false when it times out. Similarly, the call

Object head = q.poll(100, TimeUnit.MILLISECONDS)

tries for 100 milliseconds to remove the head of the queue. If it succeeds, it returns the head; otherwise, it returns null when it times out.

The put method blocks if the queue is full, and the take method blocks if the queue is empty. These are the equivalents of offer and poll with no timeout.

The java.util.concurrent package supplies several variations of blocking queues. By default, the LinkedBlockingQueue has no upper bound on its capacity, but a maximum capacity can be optionally specified. The LinkedBlockingDeque is a double-ended version. The ArrayBlockingQueue is constructed with a given capacity and an optional parameter to require fairness. If fairness is specified, then the longest-waiting threads are given preferential treatment. As always, fairness exacts a significant performance penalty, and you should only use it if your problem specifically requires it.

The PriorityBlockingQueue is a priority queue, not a first-in/first-out queue. Elements are removed in order of their priority. The queue has unbounded capacity, but retrieval will block if the queue is empty. (See Chapter 13 for more information on priority queues.)

Finally, a DelayQueue contains objects that implement the Delayed interface:

Image

The getDelay method returns the remaining delay of the object. A negative value indicates that the delay has elapsed. Elements can only be removed from a DelayQueue if their delay has elapsed. You also need to implement the compareTo method. The DelayQueue uses that method to sort the entries.

The program in Listing 14–10 shows how to use a blocking queue to control a set of threads. The program searches through all files in a directory and its subdirectories, printing lines that contain a given keyword.

A producer thread enumerates all files in all subdirectories and places them in a blocking queue. This operation is fast, and the queue would quickly fill up with all files in the file system if it was not bounded.

We also start a large number of search threads. Each search thread takes a file from the queue, opens it, prints all lines containing the keyword, and then takes the next file. We use a trick to terminate the application when no further work is required. In order to signal completion, the enumeration thread places a dummy object into the queue. (This is similar to a dummy suitcase with a label “last bag” in a baggage claim belt.) When a search thread takes the dummy, it puts it back and terminates.

Note that no explicit thread synchronization is required. In this application, we use the queue data structure as a synchronization mechanism.

Listing 14–10. BlockingQueueTest.java

Image

Image

Image

Image

Image

Image

java.util.concurrent.ArrayBlockingQueue<E> 5.0

Image

ArrayBlockingQueue(int capacity)

ArrayBlockingQueue(int capacity, boolean fair)

constructs a blocking queue with the given capacity and fairness settings. The queue is implemented as a circular array.

java.util.concurrent.LinkedBlockingQueue<E> 5.0

Image

java.util.concurrent.LinkedBlockingDeque<E> 6

Image

LinkedBlockingQueue()

LinkedBlockingDeque()

constructs an unbounded blocking queue or deque, implemented as a linked list.

LinkedBlockingQueue(int capacity)

LinkedBlockingDeque(int capacity)

constructs a bounded blocking queue or deque with the given capacity, implemented as a linked list.

java.util.concurrent.DelayQueue<E extends Delayed> 5.0

Image

• DelayQueue()

constructs an unbounded bounded blocking queue of Delayed elements. Only elements whose delay has expired can be removed from the queue.

java.util.concurrent.Delayed 5.0

Image

long getDelay(TimeUnit unit)

gets the delay for this object, measured in the given time unit.

java.util.concurrent.PriorityBlockingQueue<E> 5.0

Image

PriorityBlockingQueue()

PriorityBlockingQueue(int initialCapacity)

PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator)

constructs an unbounded blocking priority queue implemented as a heap.

Image

java.util.concurrent.BlockingQueue<E> 5.0

Image

void put(E element)

adds the element, blocking if necessary.

E take()

removes and returns the head element, blocking if necessary.

boolean offer(E element, long time, TimeUnit unit)

adds the given element and returns true if successful, blocking if necessary until the element has been added or the time has elapsed.

E poll(long time, TimeUnit unit)

removes and returns the head element, blocking if necessary until an element is available or the time has elapsed. Returns null upon failure.

java.util.concurrent.BlockingDeque<E> 6

Image

void putFirst(E element)

void putLast(E element)

adds the element, blocking if necessary.

E takeFirst()

E takeLast()

removes and returns the head or tail element, blocking if necessary.

boolean offerFirst(E element, long time, TimeUnit unit)

boolean offerLast(E element, long time, TimeUnit unit)

adds the given element and returns true if successful, blocking if necessary until the element has been added or the time has elapsed.

E pollFirst(long time, TimeUnit unit)

E pollLast(long time, TimeUnit unit)

removes and returns the head or tail element, blocking if necessary until an element is available or the time has elapsed. Returns null upon failure.

Thread-Safe Collections

If multiple threads concurrently modify a data structure such as a hash table, then it is easily possible to damage the data structure. (See Chapter 13 for more information on hash tables.) For example, one thread may begin to insert a new element. Suppose it is preempted while it is in the middle of rerouting the links between the hash table’s buckets. If another thread starts traversing the same list, it may follow invalid links and create havoc, perhaps throwing exceptions or being trapped in an infinite loop.

You can protect a shared data structure by supplying a lock, but it is usually easier to choose a thread-safe implementation instead. The blocking queues that we discussed in the preceding section are, of course, thread-safe collections. In the following sections, we discuss the other thread-safe collections that the Java library provides.

Efficient Maps, Sets, and Queues

The java.util.concurrent package supplies efficient implementations for maps, sorted sets, and queues: ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentSkipListSet, and ConcurrentLinkedQueue.

These collections use sophisticated algorithms that minimize contention by allowing concurrent access to different parts of the data structure.

Unlike in most collections, the size method does not necessarily operate in constant time. Determining the current size of one of these collections usually requires traversal.

The collections return weakly consistent iterators. That means that the iterators may or may not reflect all modifications that are made after they were constructed, but they will not return a value twice and they will not throw a ConcurrentModificationException.


Image Note

In contrast, an iterator of a collection in the java.util package throws a ConcurrentModificationException when the collection has been modified after construction of the iterator.


The concurrent hash map can efficiently support a large number of readers and a fixed number of writers. By default, it is assumed that there are up to 16 simultaneous writer threads. There can be many more writer threads, but if more than 16 write at the same time, the others are temporarily blocked. You can specify a higher number in the constructor, but it is unlikely that you will need to.

The ConcurrentHashMap and ConcurrentSkipListMap classes have useful methods for atomic insertion and removal of associations. The putIfAbsent method atomically adds a new association provided there wasn’t one before. This is useful for a cache that is accessed by multiple threads, to ensure that only one thread adds an item into the cache:

cache.putIfAbsent(key, value);

The opposite operation is remove (which perhaps should have been called removeIfPresent). The call

cache.remove(key, value)

atomically removes the key and value if they are present in the map. Finally,

cache.replace(key, oldValue, newValue)

atomically replaces the old value with the new one, provided the old value was associated with the given key.

java.util.concurrent.ConcurrentLinkedQueue<E> 5.0

Image

ConcurrentLinkedQueue<E>()

constructs an unbounded, nonblocking queue that can be safely accessed by multiple threads.

java.util.concurrent.ConcurrentSkipListSet<E> 6

Image

ConcurrentSkipListSet<E>()

ConcurrentSkipListSet<E>(Comparator<? super E> comp)

constructs a sorted set that can be safely accessed by multiple threads. The first constructor requires that the elements implement the Comparable interface.

java.util.concurrent.ConcurrentHashMap<K, V> 5.0

Image

java.util.concurrent.ConcurrentSkipListMap<K, V> 6

Image

ConcurrentHashMap<K, V>()

ConcurrentHashMap<K, V>(int initialCapacity)

ConcurrentHashMap<K, V>(int initialCapacity, float loadFactor, int concurrencyLevel)

constructs a hash map that can be safely accessed by multiple threads.

Image

ConcurrentSkipListMap<K, V>()

ConcurrentSkipListSet<K, V>(Comparator<? super K> comp)

constructs a sorted map that can be safely accessed by multiple threads. The first constructor requires that the keys implement the Comparable interface.

V putIfAbsent(K key, V value)

if the key is not yet present in the map, associates the given value with the given key and returns null. Otherwise returns the existing value associated with the key.

boolean remove(K key, V value)

if the given key is currently associated with this value, removes the given key and value and returns true. Otherwise returns false.

boolean replace(K key, V oldValue, V newValue)

if the given key is currently associated with oldValue, associates it with newValue. Otherwise, returns false.

Copy on Write Arrays

The CopyOnWriteArrayList and CopyOnWriteArraySet are thread-safe collections in which all mutators make a copy of the underlying array. This arrangement is useful if the number of threads that iterate over the collection greatly outnumbers the threads that mutate it. When you construct an iterator, it contains a reference to the current array. If the array is later mutated, the iterator still has the old array, but the collection’s array is replaced. As a consequence, the older iterator has a consistent (but potentially outdated) view that it can access without any synchronization expense.

Older Thread-Safe Collections

Ever since the initial release of Java, the Vector and Hashtable classes provided thread-safe implementations of a dynamic array and a hash table. In Java SE 1.2, these classes were declared obsolete and replaced by the ArrayList and HashMap classes. Those classes are not thread-safe. Instead, a different mechanism is supplied in the collections library. Any collection class can be made thread-safe by means of a synchronization wrapper:

List<E> synchArrayList = Collections.synchronizedList(new ArrayList<E>());
Map<K, V> synchHashMap = Collections.synchronizedMap(new HashMap<K, V>());

The methods of the resulting collections are protected by a lock, providing thread-safe access.

You should make sure that no thread accesses the data structure through the original unsynchronized methods. The easiest way to ensure this is not to save any reference to the original object. Simply construct a collection and immediately pass it to the wrapper, as we did in our examples.

You still need to use “client-side” locking if you want to iterate over the collection while another thread has the opportunity to mutate it:

Image

You must use the same code if you use a “for each” loop because the loop uses an iterator. Note that the iterator actually fails with a ConcurrentModificationException if another thread mutates the collection while the iteration is in progress. The synchronization is still required so that the concurrent modification can be reliably detected.

You are usually better off using the collections defined in the java.util.concurrent package instead of the synchronization wrappers. In particular, the ConcurrentHashMap map has been carefully implemented so that multiple threads can access it without blocking each other, provided they access different buckets. One exception is an array list that is frequently mutated. In that case, a synchronized ArrayList can outperform a CopyOnWriteArrayList.

java.util.Collections 1.2

Image

static <E> Collection<E> synchronizedCollection(Collection<E> c)

static <E> List synchronizedList(List<E> c)

static <E> Set synchronizedSet(Set<E> c)

static <E> SortedSet synchronizedSortedSet(SortedSet<E> c)

static <K, V> Map<K, V> synchronizedMap(Map<K, V> c)

static <K, V> SortedMap<K, V> synchronizedSortedMap(SortedMap<K, V> c)

constructs views of the collection whose methods are synchronized.

Callables and Futures

A Runnable encapsulates a task that runs asynchronously; you can think of it as an asynchronous method with no parameters and no return value. A Callable is similar to a Runnable, but it returns a value. The Callable interface is a parameterized type, with a single method call.

Image

The type parameter is the type of the returned value. For example, a Callable<Integer> represents an asynchronous computation that eventually returns an Integer object.

A Future holds the result of an asynchronous computation. You can start a computation, give someone the Future object, and forget about it. The owner of the Future object can obtain the result when it is ready.

The Future interface has the following methods:

Image

A call to the first get method blocks until the computation is finished. The second method throws a TimeoutException if the call timed out before the computation finished. If the thread running the computation is interrupted, both methods throw an InterruptedException. If the computation has already finished, then get returns immediately.

The isDone method returns false if the computation is still in progress, true if it is finished.

You can cancel the computation with the cancel method. If the computation has not yet started, it is canceled and will never start. If the computation is currently in progress, then it is interrupted if the mayInterrupt parameter is true.

The FutureTask wrapper is a convenient mechanism for turning a Callable into both a Future and a Runnable—it implements both interfaces. For example:

Callable<Integer> myComputation = . . .;
FutureTask<Integer> task = new FutureTask<Integer>(myComputation);
Thread t = new Thread(task); // it's a Runnable

t.start();
. . .
Integer result = task.get(); // it's a Future

The program in Listing 14–11 puts these concepts to work. This program is similar to the preceding example that found files containing a given keyword. However, now we will merely count the number of matching files. Thus, we have a long-running task that yields an integer value—an example of a Callable<Integer>.

Image

Then we construct a FutureTask object from the MatchCounter and use it to start a thread.

FutureTask<Integer> task = new FutureTask<Integer>(counter);
Thread t = new Thread(task);
t.start();

Finally, we print the result.

System.out.println(task.get() + " matching files.");

Of course, the call to get blocks until the result is actually available.

Inside the call method, we use the same mechanism recursively. For each subdirectory, we produce a new MatchCounter and launch a thread for it. We also stash the FutureTask objects away in an ArrayList<Future<Integer>>. At the end, we add up all results:

Image

Each call to get blocks until the result is available. Of course, the threads run in parallel, so there is a good chance that the results will all be available at about the same time.

Listing 14–11. FutureTest.java

Image

Image

Image

Image

Image


java.util.concurrent.Callable<V> 5.0

Image

V call()

runs a task that yields a result.

java.util.concurrent.Future<V> 5.0

Image

V get()

V get(long time, TimeUnit unit)

gets the result, blocking until it is available or the given time has elapsed. The second method throws a TimeoutException if it was unsuccessful.

boolean cancel(boolean mayInterrupt)

attempts to cancel the execution of this task. If the task has already started and the mayInterrupt parameter is true, it is interrupted. Returns true if the cancellation was successful.

boolean isCancelled()

returns true if the task was canceled before it completed.

boolean isDone()

returns true if the task completed, through normal completion, cancellation, or an exception.

java.util.concurrent.FutureTask<V> 5.0

Image

FutureTask(Callable<V> task)

FutureTask(Runnable task, V result)

constructs an object that is both a Future<V> and a Runnable.

Executors

Constructing a new thread is somewhat expensive because it involves interaction with the operating system. If your program creates a large number of short-lived threads, then it should instead use a thread pool. A thread pool contains a number of idle threads that are ready to run. You give a Runnable to the pool, and one of the threads calls the run method. When the run method exits, the thread doesn’t die but stays around to serve the next request.

Another reason to use a thread pool is to throttle the number of concurrent threads. Creating a huge number of threads can greatly degrade performance and even crash the virtual machine. If you have an algorithm that creates lots of threads, then you should use a “fixed” thread pool that bounds the total number of concurrent threads.

The Executors class has a number of static factory methods for constructing thread pools; see Table 14–2 for a summary.

Table 14–2. Executors Factory Methods

Image

Thread Pools

Let us look at the first three methods in Table 14–2. We discuss the remaining methods in the section “Scheduled Execution” on page 783. The newCachedThreadPool method constructs a thread pool that executes each task immediately, using an existing idle thread when available and creating a new thread otherwise. The newFixedThreadPool method constructs a thread pool with a fixed size. If more tasks are submitted than there are idle threads, then the unserved tasks are placed on a queue. They are run when other tasks have completed. The newSingleThreadExecutor is a degenerate pool of size 1: A single thread executes the submitted tasks, one after another. These three methods return an object of the ThreadPoolExecutor class that implements the ExecutorService interface.

You can submit a Runnable or Callable to an ExecutorService with one of the following methods:

Future<?> submit(Runnable task)
Future<T> submit(Runnable task, T result)
Future<T> submit(Callable<T> task)

The pool will run the submitted task at its earliest convenience. When you call submit, you get back a Future object that you can use to query the state of the task.

The first submit method returns an odd-looking Future<?>. You can use such an object to call isDone, cancel, or isCancelled. But the get method simply returns null upon completion.

The second version of submit also submits a Runnable, and the get method of the Future returns the given result object upon completion.

The third version submits a Callable, and the returned Future gets the result of the computation when it is ready.

When you are done with a thread pool, call shutdown. This method initiates the shutdown sequence for the pool. An executor that is shut down accepts no new tasks. When all tasks are finished, the threads in the pool die. Alternatively, you can call shutdownNow. The pool then cancels all tasks that have not yet begun and attempts to interrupt the running threads.

Here, in summary, is what you do to use a connection pool:

1. Call the static newCachedThreadPool or newFixedThreadPool method of the Executors class.

2. Call submit to submit Runnable or Callable objects.

3. If you want to be able to cancel a task or if you submit Callable objects, hang on to the returned Future objects.

4. Call shutdown when you no longer want to submit any tasks.

For example, the preceding example program produced a large number of short-lived threads, one per directory. The program in Listing 14–12 uses a thread pool to launch the tasks instead.

For informational purposes, this program prints out the largest pool size during execution. This information is not available through the ExecutorService interface. For that reason, we had to cast the pool object to the ThreadPoolExecutor class.

Listing 14–12. ThreadPoolTest.java

Image

Image

Image

Image

java.util.concurrent.Executors 5.0

Image

ExecutorService newCachedThreadPool()

returns a cached thread pool that creates threads as needed and terminates threads that have been idle for 60 seconds.

ExecutorService newFixedThreadPool(int threads)

returns a thread pool that uses the given number of threads to execute tasks.

ExecutorService newSingleThreadExecutor()

returns an executor that executes tasks sequentially in a single thread.

java.util.concurrent.ExecutorService 5.0

Image

Future<T> submit(Callable<T> task)

Future<T> submit(Runnable task, T result)

Future<?> submit(Runnable task)

submits the given task for execution.

void shutdown()

shuts down the service, completing the already submitted tasks but not accepting new submissions.

java.util.concurrent.ThreadPoolExecutor 5.0

Image

int getLargestPoolSize()

returns the largest size of the thread pool during the life of this executor.

Scheduled Execution

The ScheduledExecutorService interface has methods for scheduled or repeated execution of tasks. It is a generalization of java.util.Timer that allows for thread pooling. The newScheduledThreadPool and newSingleThreadScheduledExecutor methods of the Executors class return objects that implement the ScheduledExecutorService interface.

You can schedule a Runnable or Callable to run once, after an initial delay. You can also schedule a Runnable to run periodically. See the API notes for details.

java.util.concurrent.Executors 5.0

Image

ScheduledExecutorService newScheduledThreadPool(int threads)

returns a thread pool that uses the given number of threads to schedule tasks.

ScheduledExecutorService newSingleThreadScheduledExecutor()

returns an executor that schedules tasks in a single thread.

java.util.concurrent.ScheduledExecutorService 5.0

Image

ScheduledFuture<V> schedule(Callable<V> task, long time, TimeUnit unit)

ScheduledFuture<?> schedule(Runnable task, long time, TimeUnit unit)

schedules the given task after the given time has elapsed.

ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit)

schedules the given task to run periodially, every period units, after the initial delay has elapsed.

ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long initialDelay, long delay, TimeUnit unit)

schedules the given task to run periodially, with delay units between completion of one invocation and the start of the next, after the initial delay has elapsed.

Controlling Groups of Tasks

You have seen how to use an executor service as a thread pool to increase the efficiency of task execution. Sometimes, an executor is used for a more tactical reason, simply to control a group of related tasks. For example, you can cancel all tasks in an executor with the shutdownNow method.

The invokeAny method submits all objects in a collection of Callable objects and returns the result of a completed task. You don’t know which task that is—presumably, it was the one that finished most quickly. You would use this method for a search problem in which you are willing to accept any solution. For example, suppose that you need to factor a large integer—a computation that is required for breaking the RSA cipher. You could submit a number of tasks, each of which attempts a factorization by using numbers in a different range. As soon as one of these tasks has an answer, your computation can stop.

The invokeAll method submits all objects in a collection of Callable objects and returns a list of Future objects that represent the solutions to all tasks. You can process the results of the computation when they are available, like this:

Image

A disadvantage with this approach is that you may wait needlessly if the first task happens to take a long time. It would make more sense to obtain the results in the order in which they are available. This can be arranged with the ExecutorCompletionService.

Start with an executor, obtained in the usual way. Then construct an ExecutorCompletionService. Submit tasks to the completion service. The service manages a blocking queue of Future objects, containing the results of the submitted tasks as they become available. Thus, a more efficient organization for the preceding computation is the following:

Image

java.util.concurrent.ExecutorService 5.0

Image

T invokeAny(Collection<Callable<T>> tasks)

T invokeAny(Collection<Callable<T>> tasks, long timeout, TimeUnit unit)

executes the given tasks and returns the result of one of them. The second method throws a TimeoutException if a timeout occurs.

List<Future<T>> invokeAll(Collection<Callable<T>> tasks)

List<Future<T>> invokeAll(Collection<Callable<T>> tasks, long timeout, TimeUnit unit)

executes the given tasks and returns the results of all of them. The second method throws a TimeoutException if a timeout occurs.

java.util.concurrent.ExecutorCompletionService 5.0

Image

ExecutorCompletionService(Executor e)

constructs an executor completion service that collects the results of the given executor.

Future<T> submit(Callable<T> task)

Future<T> submit(Runnable task, T result)

submits a task to the underlying executor.

Future<T> take()

removes the next completed result, blocking if no completed results are available.

Future<T> poll()

Future<T> poll(long time, TimeUnit unit)

removes the next completed result or null if no completed results are available. The second method waits for the given time.

Synchronizers

The java.util.concurrent package contains several classes that help manage a set of collaborating threads—see Table 14–3. These mechanisms have “canned functionality” for common rendezvous patterns between threads. If you have a set of collaborating threads that follows one of these behavior patterns, you should simply reuse the appropriate library class instead of trying to come up with a handcrafted collection of locks and conditions.

Table 14–3. Synchronizers

Image

Image

Semaphores

Conceptually, a semaphore manages a number of permits. To proceed past the semaphore, a thread requests a permit by calling acquire. Only a fixed number of permits are available, limiting the number of threads that are allowed to pass. Other threads may issue permits by calling release. There are no actual permit objects. The semaphore simply keeps a count. Moreover, a permit doesn’t have to be released by the thread that acquires it. In fact, any thread can issue any number of permits. If it issues more than the maximum available, the semaphore is simply set to the maximum count. This generality makes semaphores both very flexible and potentially confusing.

Semaphores were invented by Edsger Dijkstra in 1968, for use as a synchronization primitive. Dijkstra showed that semaphores can be efficiently implemented and that they are powerful enough to solve many common thread synchronization problems. In just about any operating systems textbook, you will find implementations of bounded queues using semaphores. Of course, application programmers shouldn’t reinvent bounded queues. We suggest that you only use semaphores when their behavior maps well onto your synchronization problem, without your going through mental contortions.

One simple example is a semaphore with a permit count of 1. Such a semaphore can be used as a gate for one thread that is opened and closed by another thread. In the section “Example: Pausing and Resuming an Animation” on page 788, you will see an example in which a worker thread produces an animation. Occasionally, the worker thread waits for the user to press a button. The worker thread tries to acquire a permit, and it has to wait until the button click causes a permit to be issued.

Countdown Latches

A CountDownLatch lets a set of threads wait until a count has reached zero. The countdown latch is one-time only. Once the count has reached 0, you cannot increment it again.

A useful special case is a latch with a count of 1. This implements a one-time gate. Threads are held at the gate until another thread sets the count to 0.

Imagine, for example, a set of threads that need some initial data to do their work. The worker threads are started and wait at the gate. Another thread prepares the data. When it is ready, it calls countDown, and all worker threads proceed.

You can then use a second latch to check when all worker threads are done. Initialize the latch with the number of threads. Each worker thread counts down that latch just before it terminates. Another thread that harvests the work results waits on the latch, and it proceeds as soon as all workers have terminated.

Barriers

The CyclicBarrier class implements a rendezvous called a barrier. Consider a number of threads that are working on parts of a computation. When all parts are ready, the results need to be combined. When a thread is done with its part, we let it run against the barrier. Once all threads have reached the barrier, the barrier gives way and the threads can proceed.

Here are the details. First, construct a barrier, giving the number of participating threads:

CyclicBarrier barrier = new CyclicBarrier(nthreads);

Each thread does some work and calls await on the barrier upon completion:

Image

The await method takes an optional timeout parameter:

barrier.await(100, TimeUnit.MILLISECONDS);

If any of the threads waiting for the barrier leaves the barrier, then the barrier breaks. (A thread can leave because it called await with a timeout or because it was interrupted.) In that case, the await method for all other threads throws a BrokenBarrierException. Threads that are already waiting have their await call terminated immediately.

You can supply an optional barrier action that is executed when all threads have reached the barrier:

Runnable barrierAction = . . .;
CyclicBarrier barrier = new CyclicBarrier(nthreads, barrierAction);

The action can harvest the result of the individual threads.

The barrier is called cyclic because it can be reused after all waiting threads have been released. In this regard, it differs from a CountDownLatch, which can only be used once.

Exchangers

An Exchanger is used when two threads are working on two instances of the same data buffer. Typically, one thread fills the buffer, and the other consumes its contents. When both are done, they exchange their buffers.

Synchronous Queues

A synchronous queue is a mechanism that pairs up producer and consumer threads. When a thread calls put on a SynchronousQueue, it blocks until another thread calls take, and vice versa. Unlike the case with an Exchanger, data are only transferred in one direction, from the producer to the consumer.

Even though the SynchronousQueue class implements the BlockingQueue interface, it is not conceptually a queue. It does not contain any elements—its size method always returns 0.

Example: Pausing and Resuming an Animation

Consider a program that does some work, updates the screen display, then waits for the user to look at the result and press a button to continue, and then does the next unit of work.

A semaphore with a permit count of 1 can be used to synchronize the worker thread and the event dispatch thread. The worker thread calls acquire whenever it is ready to pause. The GUI thread calls release whenever the user clicks the Continue button.

What happens if the user clicks the button multiple times while the worker thread is ready? Because only one permit is available, the permit count stays at 1.

The program in Listing 14–13 puts this idea to work. The program animates a sorting algorithm. A worker thread sorts an array, stopping periodically and waiting for the user to give permission to proceed. The user can admire a painting of the current state of the algorithm and press the Continue button to allow the worker thread to go to the next step.

We didn’t want to bore you with the code for a sorting algorithm, so we simply call Arrays.sort, which implements the merge sort algorithm. To pause the algorithm, we supply a Comparator object that waits for the semaphore. Thus, the animation is paused whenever the algorithm compares two elements. We paint the current values of the array and highlight the elements that are being compared (see Figure 14–8).

Figure 14–8. Animating a sort algorithm

Image


Image Note

The animation shows the merging of smaller sorted ranges into larger ones, but it is not entirely accurate. The mergesort algorithm uses a second array for holding temporary values that we do not get to see. The point of this example is not to delve into sorting algorithms, but to show how to use a semaphore for pausing a worker thread.


Listing 14–13. AlgorithmAnimation.java

Image

Image

Image

Image

Image

Image

Image


java.util.concurrent.CyclicBarrier 5.0

Image

CyclicBarrier(int parties)

CyclicBarrier(int parties, Runnable barrierAction)

constructs a cyclic barrier for the given number of parties. The barrierAction is executed when all parties have called await on the barrier.

int await()

int await(long time, TimeUnit unit)

waits until all parties have called await on the barrier or until the timeout has been reached, in which case a TimeoutException is thrown. Upon success, returns the arrival index of this party. The first party has index parties - 1, and the last party has index 0.


java.util.concurrent.CountDownLatch 5.0

Image

CountdownLatch(int count)

constructs a countdown latch with the given count.

void await()

waits for this latch to count down to 0.

boolean await(long time, TimeUnit unit)

waits for this latch to count down to 0 or for the timeout to elapse. Returns true if the count is 0, false if the timeout elapsed.

public void countDown()

counts down the counter of this latch.

java.util.concurrent.Exchanger<V> 5.0

Image

V exchange(V item)

V exchange(V item, long time, TimeUnit unit)

blocks until another thread calls this method, and then exchanges the item with the other thread and returns the other thread’s item. The second method throws a TimeoutException after the timeout has elapsed.

java.util.concurrent.SynchronousQueue<V> 5.0

Image

SynchronousQueue()

SynchronousQueue(boolean fair)

constructs a synchronous queue that allows threads to hand off items. If fair is true, the queue favors the longest-waiting threads.

void put(V item)

blocks until another thread calls take to take this item.

V take()

blocks until another thread calls put. Returns the item that the other thread provided.

java.util.concurrent.Semaphore 5.0

Image

Semaphore(int permits)

Semaphore(int permits, boolean fair)

constructs a semaphore with the given maximum number of permits. If fair is true, the queue favors the longest-waiting threads.

void acquire()

waits to acquire a permit.

boolean tryAcquire()

tries to acquire a permit; returns false if none is available.

boolean tryAcquire(long time, TimeUnit unit)

tries to acquire a permit within the given time; returns false if none is available.

void release()

releases a permit.

Threads and Swing

As we mentioned in the introduction to this chapter, one of the reasons to use threads in your programs is to make your programs more responsive. When your program needs to do something time consuming, then you should fire up another worker thread instead of blocking the user interface.

However, you have to be careful what you do in a worker thread because, perhaps surprisingly, Swing is not thread safe. If you try to manipulate user interface elements from multiple threads, then your user interface can become corrupted.

To see the problem, run the upcoming test program in Listing 14–14. When you click the Bad button, a new thread is started whose run method tortures a combo box, randomly adding and removing values.

Image

Try it out. Click the Bad button. Click the combo box a few times. Move the scrollbar. Move the window. Click the Bad button again. Keep clicking the combo box. Eventually, you should see an exception report (see Figure 14–9).

Figure 14–9. Exception reports in the console

Image

What is going on? When an element is inserted into the combo box, the combo box fires an event to update the display. Then, the display code springs into action, reading the current size of the combo box and preparing to display the values. But the worker thread keeps going—occasionally resulting in a reduction of the count of the values in the combo box. The display code then thinks that there are more values in the model than there actually are, asks for nonexistent values, and triggers an ArrayIndexOutOfBounds exception.

This situation could have been avoided by enabling programmers to lock the combo box object while displaying it. However, the designers of Swing decided not to expend any effort to make Swing thread safe, for two reasons. First, synchronization takes time, and nobody wanted to slow down Swing any further. More important, the Swing team checked out the experience other teams had with thread-safe user interface toolkits. What they found was not encouraging. Programmers using thread-safe toolkits turned out to be confused by the demands for synchronization and often created deadlock-prone programs.

Running Time-Consuming Tasks

When you use threads together with Swing, you have to follow two simple rules.

• If an action takes a long time, do it in a separate worker thread and never in the event dispatch thread.

• Do not touch Swing components in any thread other than the event dispatch thread.

The reason for the first rule is easy to understand. If you take a long time in the event dispatch thread, the application seems “dead” because it cannot respond to any events. In particular, the event dispatch thread should never make input/output calls, which might block indefinitely, and it should never call sleep. (If you need to wait for a specific amount of time, use timer events.)

The second rule is often called the single-thread rule for Swing programming. We discuss it further on page 806.

These two rules seem to be in conflict with each other. Suppose you fire up a separate thread to run a time-consuming task. You usually want to update the user interface to indicate progress while your thread is working. When your task is finished, you want to update the GUI again. But you can’t touch Swing components from your thread. For example, if you want to update a progress bar or a label text, then you can’t simply set its value from your thread.

To solve this problem, you can use two utility methods in any thread to add arbitrary actions to the event queue. For example, suppose you want to periodically update a label in a thread to indicate progress. You can’t call label.setText from your thread.

Instead, use the invokeLater and invokeAndWait methods of the EventQueue class to have that call executed in the event dispatching thread.

Here is what you do. You place the Swing code into the run method of a class that implements the Runnable interface. Then, you create an object of that class and pass it to the static invokeLater or invokeAndWait method. For example, here is how to update a label text:

Image

The invokeLater method returns immediately when the event is posted to the event queue. The run method is executed asynchronously. The invokeAndWait method waits until the run method has actually been executed.

In the situation of updating a progress label, the invokeLater method is more appropriate. Users would rather have the worker thread make more progress than have the most precise progress indicator.

Both methods execute the run method in the event dispatch thread. No new thread is created.

Listing 14–14 demonstrates how to use the invokeLater method to safely modify the contents of a combo box. If you click on the Good button, a thread inserts and removes numbers. However, the actual modification takes place in the event dispatching thread.

Listing 14–14. SwingThreadTest.java

Image

Image

Image

Image

Image

Image

java.awt.EventQueue 1.1

Image

static void invokeLater(Runnable runnable) 1.2

causes the run method of the runnable object to be executed in the event dispatch thread after pending events have been processed.

static void invokeAndWait(Runnable runnable) 1.2

causes the run method of the runnable object to be executed in the event dispatch thread after pending events have been processed. This call blocks until the run method has terminated.

static boolean isDispatchThread() 1.2

returns true if the thread executing this method is the event dispatch thread.

Using the Swing Worker

When a user issues a command for which processing takes a long time, you will want to fire up a new thread to do the work. As you saw in the preceding section, that thread should use the EventQueue.invokeLater method to update the user interface.

Several authors have produced convenience classes to ease this programming task, and one of these classes has made its way into Java SE 6. In this section, we describe that SwingWorker class.

The program in Listing 14–15 has commands for loading a text file and for canceling the file loading process. You should try the program with a long file, such as the full text of The Count of Monte Cristo, supplied in the gutenberg directory of the book’s companion code. The file is loaded in a separate thread. While the file is read, the Open menu item is disabled and the Cancel item is enabled (see Figure 14–10). After each line is read, a line counter in the status bar is updated. After the reading process is complete, the Open menu item is reenabled, the Cancel item is disabled, and the status line text is set to Done.

Figure 14–10. Loading a file in a separate thread

Image

This example shows the typical UI activities of a background task:

• After each work unit, update the UI to show progress.

• After the work is finished, make a final change to the UI.

The SwingWorker class makes it easy to implement such a task. You override the doInBackground method to do the time-consuming work and occasionally call publish to communicate work progress. This method is executed in a worker thread. The publish method causes a process method to execute in the event dispatch thread to deal with the progress data. When the work is complete, the done method is called in the event dispatch thread so that you can finish updating the UI.

Whenever you want to do some work in the worker thread, construct a new worker. (Each worker object is meant to be used only once.) Then call the execute method. You will typically call execute on the event dispatch thread, but that is not a requirement.

It is assumed that a worker produces a result of some kind; therefore, SwingWorker<T, V> implements Future<T>. This result can be obtained by the get method of the Future interface. Since the get method blocks until the result is available, you don’t want to call it immediately after calling execute. It is a good idea to call it only when you know that the work has been completed. Typically, you call get from the done method. (There is no requirement to call get. Sometimes, processing the progress data is all you need.)

Both the intermediate progress data and the final result can have arbitrary types. The SwingWorker class has these types as type parameters. A SwingWorker<T, V> produces a result of type T and progress data of type V.

To cancel the work in progress, use the cancel method of the Future interface. When the work is canceled, the get method throws a CancellationException.

As already mentioned, the worker thread’s call to publish will cause calls to process on the event dispatch thread. For efficiency, the results of several calls to publish may be batched up in a single call to process. The process method receives a List<V> containing all intermediate results.

Let us put this mechanism to work for reading in a text file. As it turns out, a JTextArea is quite slow. Appending lines from a long text file (such as all lines in The Count of Monte Cristo) takes considerable time.

To show the user that progress is being made, we want to display the number of lines read in a status line. Thus, the progress data consist of the current line number and the current line of text. We package these into a trivial inner class:

Image

The final result is the text that has been read into a StringBuilder. Thus, we need a SwingWorker<StringBuilder, ProgressData>.

In the doInBackground method, we read a file, a line at a time. After each line, we call publish to publish the line number and the text of the current line.

Image

We also sleep for a millisecond after every line so that you can test cancellation without getting stressed out, but you wouldn’t want to slow down your own programs by sleeping. If you comment out this line, you will find that The Count of Monte Cristo loads quite quickly, with only a few batched user interface updates.


Image Note

You can make this program behave quite smoothly by updating the text area from the worker thread, but this is not possible for most Swing components. We show you the general approach in which all component updates occur in the event dispatch thread.


In the process method, we ignore all line numbers but the last one, and we concatenate all lines for a single update of the text area.

Image

In the done method, the text area is updated with the complete text, and the Cancel menu item is disabled.

Note how the worker is started in the event listener for the Open menu item.

This simple technique allows you to execute time-consuming tasks while keeping the user interface responsive.

Listing 14–15. SwingWorkerTest.java

Image

Image

Image

Image

Image

Image

Image

javax.swing.SwingWorker<T, V> 6

Image

abstract T doInBackground()

override this method to carry out the background task and to return the result of the work.

void process(List<V> data)

override this method to process intermediate progress data in the event dispatch thread.

void publish(V... data)

forwards intermediate progress data to the event dispatch thread. Call this method from doInBackground.

void execute()

schedules this worker for execution on a worker thread.

SwingWorker.StateValue getState()

gets the state of this worker, one of PENDING, STARTED, or DONE.

The Single-Thread Rule

Every Java application starts with a main method that runs in the main thread. In a Swing program, the main thread is short-lived. It schedules the construction of the user interface in the event dispatch thread and then exits. After the user interface construction, the event dispatch thread processes event notifications, such as calls to actionPerformed or paintComponent. Other threads, such as the thread that posts events into the event queue, are running behind the scenes, but those threads are invisible to the application programmer.

Earlier in the chapter, we introduced the single-thread rule: “Do not touch Swing components in any thread other than the event dispatch thread.” In this section, we investigate that rule further.

There are a few exceptions to the single-thread rule.

• You can safely add and remove event listeners in any thread. Of course, the listener methods will be invoked in the event dispatch thread.

• A small number of Swing methods are thread safe. They are specially marked in the API documentation with the sentence “This method is thread safe, although most Swing methods are not.” The most useful among these thread-safe methods are

JTextComponent.setText
JTextArea.insert
JTextArea.append
JTextArea.replaceRange
JComponent.repaint
JComponent.revalidate


Image Note

We used the repaint method many times in this book, but the revalidate method is less common. Its purpose is to force a layout of a component after the contents have changed. The traditional AWT has a validate method to force the layout of a component. For Swing components, you should simply call revalidate instead. (However, to force the layout of a JFrame, you still need to call validate—a JFrame is a Component but not a JComponent.)


Historically, the single-thread rule was more permissive. Any thread was allowed to construct components, set their properties, and add them into containers, as long as none of the components had been realized. A component is realized if it can receive paint or validation events. This is the case as soon as the setVisible(true) or pack (!) methods have been invoked on the component, or if the component has been added to a container that has been realized.

That version of the single-thread rule was convenient. It allowed you to create the GUI of in the main method and then call setVisible(true) on the top-level frame of the application. There was no bothersome scheduling of a Runnable on the event dispatch thread.

Unfortunately, some component implementors did not pay attention to the subtleties of the original single-thread rule. They launched activities on the event dispatch thread without ever bothering to check whether the component was realized. For example, if you call setSelectionStart or setSelectionEnd on a JTextComponent, a caret movement is scheduled in the event dispatch thread, even if the component is not visible.

It might well have been possible to detect and fix these problems, but the Swing designers took the easy way out. They decreed that it is never safe to access components from any thread other than the event dispatch thread. Therefore, you need to construct the user interface in the event dispatch thread, using the call to EventQueue.invokeLater that you have seen in all our sample programs.

Of course, there are plenty of programs that are not so careful and live by the old version of the single-thread rule, initializing the user interface on the main thread. Those programs incur the slight risk that some of the user interface initialization causes actions on the event dispatch thread that conflict with actions on the main thread. As we said in Chapter 7, you don’t want to be one of the unlucky few who run into trouble and waste time debugging an intermittent threading bug. Therefore, you should simply follow the strict single-thread rule.

You have now reached the end of Volume I of Core Java. This volume covered the fundamentals of the Java programming language and the parts of the standard library that you need for most programming projects. We hope that you enjoyed your tour through the Java fundamentals and that you found useful information along the way. For advanced topics, such as networking, advanced AWT/Swing, security, and internationalization, please turn to Volume II.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.184.102