Programming Threads
Overview
Remember the guy from the old Ed Sullivan Show who used to spin plates? Somehow he managed to keep all those plates spinning, running from pole to pole to give each plate a little nudge-just enough to keep it going.
In Java, threads are the equivalent of the spinning plate guy. Threads let you divide the work of an application into separate pieces, which then all run simultaneously. The result is a faster and more efficient program, but along with the increased speed comes more difficult programming and debugging.
Truthfully, the subtleties of threaded programming are a topic for Computer Science majors. But the basics of working with threads aren't all that difficult to learn. In this chapter, I focus on those basics and leave the advanced techniques for the grad students.
Warning |
The main application I use to illustrate threading in this chapter simulates the countdown clock for the space shuttle. Working with threads isn't really rocket science, but threading is used to solve difficult programming problems. You invariably find yourself trying to get two or more separate pieces of code to coordinate their activities, and that's not as easy as you might think at first guess. As a result, I can't possibly talk about threading without getting into some challenging mental exercises. So be prepared to spend some mental energy figuring out how it works. |
REMEMBER |
The listings in this chapter as well as throughout the book are available at http://www.dummies.com/go/javaaiofd2e. |
Understanding Threads
A thread is a single sequence of executable code within a larger program. All the programs shown so far in this book have used just one thread-the main thread that starts automatically when you run the program. However, Java lets you create programs that start additional threads to perform specific tasks.
You're probably already familiar with programs that use threads to perform several tasks at once. Here are some common examples:
- Web browsers can download files while still letting you view Web pages. When you download a file in a Web browser, the browser starts a separate thread to handle the download.
- E-mail programs don't make you wait for all your messages to download before you can read the first message. Instead, these programs use separate threads to display and download messages.
- Word processors can print long documents in the background while you continue to work. These programs start a separate thread to handle print jobs.
- Word processors can also check your spelling as you type. Depending on how the word processor is written, it may run the spell check in a separate thread.
- Game programs commonly use several threads to handle different parts of the game to improve the overall responsiveness of the game.
-
Tip All GUI-based programs use at least two threads-one thread to run the application's main logic, and another thread to monitor mouse and keyboard events. You find out about creating GUI programs in Java in Book VI.
- Indeed, the Java Virtual Machine itself uses threading for some of its housekeeping chores. For example, the garbage collector runs as a separate thread so it can constantly monitor the state of the VM's memory and decide when it needs to create some free memory by removing objects that are no longer being used.
Creating a Thread
Suppose you're developing software for NASA, and you're in charge of the program that controls the final 31 seconds of the countdown for the space shuttle. Your software has to coordinate several key events that occur when the clock reaches certain points:
- T-minus 16 seconds: Flood launch pad. This releases 350,000 gallons of water onto the launch pad, which helps protect the shuttle systems during launch. (I'm not making this part up.)
- T-minus 6 seconds: Start the main engines. In the real space shuttle, the three engines are not started all at the same time. Instead, engine 1 is started at T-minus 6.6 seconds, engine 2 is started 120 milliseconds later at T-minus 6.48 seconds, and engine 3 is started 120 milliseconds after that at T-minus 6.36 seconds. I fudge in my program and start all three engines at T-minus 6 seconds.
- T-minus 0: Lift off! The solid rocket boosters are lit, the clamps are released, and the shuttle flies into space.
For this program, I don't actually start any rocket engines or release huge amounts of water. Instead, I just display messages on the console to simulate these events. But I do create four separate threads to make everything work. One thread manages the countdown clock. The other three threads fire off their respective events at T-minus 16 seconds (flood the pad), T-minus 6 seconds (fire the engines), and T-minus 0 (launch).
For the first attempt at this program, I just get the countdown clock up and running. The countdown clock is represented by a class named CountDownClock. All this class does is count down from 20 to 0 at one-second intervals, displaying messages such as T minus 20 on the console as it counts. This version of the program doesn't do much of anything, but it does demonstrate how to get a thread going. But first, I need to have a look at the Thread class.
Understanding the Thread class
The Thread class lets you create an object that can be run as a thread in a multi-threaded Java application. The Thread class has quite a few constructors and methods, but for most applications you only need to use the ones listed in Table 1-1. (Note that this table is here to give you an overview of the Thread class and to serve as a reference. Don't worry about the details of each constructor and method just yet. By the end of this chapter, I explain each of the constructors and methods.)
Constructor |
Explanation |
---|---|
Thread() |
The basic Thread constructor without parameters. This constructor simply creates an instance of the Thread class. |
Thread (String name) |
Creates a Thread object and assigns the specified name to the thread. |
Thread (Runnable target) |
A more advanced constructor that lets you turn any object that implements an API interface called Runnable into a thread. You see how this constructor is used later in this chapter. |
Thread (Runnable target, String name) |
Creates a thread from any object that implements Runnable and assigns the specified name to the thread. |
static int activeCount () |
Returns the number of active threads. |
static int enumerate (Thread [] t) |
Fills the specified array with a copy of each active thread. The return value is the number of threads added to the array. |
String getName () |
Returns the name of the thread. |
int getPriority() |
Returns the thread's priority. |
void interrupt () |
Interrupts this thread. |
boolean isinterrupted() |
Checks whether the thread has been interrupted. |
void setPriority (int priority) |
Sets the thread's priority. |
void setName (String name) |
Sets the thread's name. |
static void Sleep |
Causes the currently executing thread (int milliseconds) to sleepforthe specified number of milliseconds. |
void run() |
This method is called when the thread is started. Place the code that you want the thread to execute inside this method. |
void start () |
Starts the thread. |
static void yield () |
Causes the currently executing thread to yield to other threads that are waiting to execute. |
Extending the Thread class
The easiest way to create a thread is to write a class that extends the Thread class. Then, all you have to do to start a thread is create an instance of your thread class and call its start method.
For example, Listing 1-1 is a version of the CountDownClock class that extends the Thread class.
Listing 1-1: The CountDownClock Class (Version 1)
public class CountDownClock extends Thread → 1 { public void run() → 3 { for (int t = 20; t >= 0; t--) → 5 { System.out.println("T minus " + t); try { Thread.sleep(1000); →10 } catch (InterruptedException e) {} } } }
Here are a few key points to notice in this class:
→ 1 |
The CountDownClock class extends the Thread class. Thread is defined in the java.language package, so you don't have to provide an import statement to use it. |
→ 3 |
The CountDownClock class has a single method, named run. This method is called by Java when the clock thread has been started. All the processing done by the thread must either be in the run method or in some other method called by the run method. |
→ 5 |
The run method includes a for loop that counts down from 20 to 0. |
→ 10 |
The CountDownClock class uses the sleep method to pause for one second. Because the sleep method throws InterruptedException, a try/catch statement handles this exception. If the exception is caught, it is simply ignored. |
REMEMBER |
At some point in its execution, the run method must either call sleep or yield to give other threads a chance to execute. |
Creating and starting a thread
After you define a class that defines a Thread object, you can create and start the thread. For example, here's the main class for the first version of the countdown application:
public class CountDownApp { public static void main(String[] args) { Thread clock = new CountDownClock(); clock.start(); } }
Here a variable of type Thread is declared, and an instance of the CountDownClock is created and assigned to it. This creates a Thread object, but the thread doesn't begin executing until you call its start method.
When you run this program, the thread starts counting down in one-second increments, displaying messages such as the following on the console:
T-minus 20 T-minus 19 T-minus 18
And so on, all the way to zero. So far, so good.
Implementing the Runnable Interface
For the threads that trigger specific countdown events such as flooding the launch pad, starting the events, or lifting off, I create another class called LaunchEvent. This class uses another technique for creating and starting threads, one that requires a few more lines of code but is more flexible.
The problem with creating a class that extends the Thread class is that a class can have one superclass. What if you'd rather have your thread object extend some other class? In that case, you can create a class that implements the Runnable interface rather than extends the Thread class. The Runnable interface marks an object that can be run as a thread. It has only one method, run, that contains the code that's executed in the thread. (The Thread class itself implements Runnable, which is why the Thread class has a run method.)
Using the Runnable interface
To use the Runnable interface to create and start a thread, you have to do the following:
- Create a class that implements Runnable.
- Provide a run method in the Runnable class.
- Create an instance of the Thread class and pass your Runnable object to its constructor as a parameter.
A Thread object is created that can run your Runnable class.
- Call the Thread object's start method.
The run method of your Runnable object is called, which executes in a separate thread.
The first two of these steps are easy. The trick is in the third and fourth steps, because you can complete them several ways. Here's one way, assuming that your Runnable class is named RunnableClass:
RunnableClass rc = new RunnableClass(); Thread t = new Thread(rc); t.start();
Java programmers like to be as concise as possible, so you often see this compressed to something more like this:
Thread t = new Thread(new RunnableClass()); t.start();
or even just this:
new Thread(new RunnableClass()).start();
This single-line version works-provided you don't need to access the thread object later in the program.
Creating a class that implements Runnable
To sequence the launch events for the NASA application, I create a Runnable object named LaunchEvent. The constructor for this class accepts two parameters: the countdown time at which the event fires, and the message that is displayed when the time arrives. Then, the run method for this class uses Thread.sleep to wait until the desired time arrives. Then it displays the message.
Listing 1-2 shows the code for this class.
Listing 1-2: The LaunchEvent Class (Version 1)
public class LaunchEvent implements Runnable → 1 { private int start; private String message; public LaunchEvent(int start, String message) → 6 { this.start = start; this.message = message; } public void run() { try { Thread.sleep(20000 - (start * 1000)); → 16 } catch (InterruptedException e) {} System.out.println(message); → 20 } }
The following paragraphs draw your attention to the listing's key lines:
→ 1 |
This class implements the Runnable interface. |
→ 6 |
The constructor accepts two parameters: an integer representing the start time (in seconds) and a string message that's displayed when the time arrives. The constructor simply stores these parameter values in private fields. |
→ 16 |
In the run method, the Thread.sleep method is called to put the thread to sleep until the desired countdown time arrives. This length of time the thread should sleep is calculated by the expression 20000 − (start * 1000). The countdown clock starts at 20 seconds, which is 20,000 milliseconds. This expression simply subtracts the number of milliseconds that corresponds to the desired start time from 20,000. Thus, if the desired start time is 6 seconds, the sleep method sleeps for 14,000 milliseconds-that is, 14 seconds. |
→ 20 |
When the thread wakes up, it displays the message passed via its constructor on the console. |
Using the CountDownApp class
Now that you've seen the code for the LaunchEvent and CountDownClock classes, Listing 1-3 shows the code for a CountDownApp class that uses these classes to launch a space shuttle.
Listing 1-3: The CountDownApp Class (Version 1)
public class CountDownApp { public static void main(String[] args) { Thread clock = new CountDownClock(); → 5 Runnable flood, ignition, liftoff; → 7 flood = new LaunchEvent(16, "Flood the pad!"); ignition = new LaunchEvent(6, "Start engines!"); liftoff = new LaunchEvent(0, "Liftoff!"); clock.start(); → 12 new Thread(flood).start(); → 14 new Thread(ignition).start(); new Thread(liftoff).start(); } }
The following paragraphs summarize how this program works:
→ 5 |
The main method starts by creating an instance of the CountDownClock class and saving it in the clock variable. |
→ 7 |
Next it creates three LaunchEvent objects to flood the pad at 16 seconds, start the engines at 6 seconds, and lift off at 0 seconds. These objects are assigned to variables of type Runnable named flood, ignition, and liftoff. |
→ 12 |
The clock thread is started. The countdown starts ticking. |
→ 14 |
Finally, the program starts the three LaunchEvent objects as threads. It does this by creating a new instance of the Thread class, passing the LaunchEvent objects as parameters to the Thread constructor, and then calling the start method to start the thread. Note that because this program doesn't need to do anything with these threads once they're started, it doesn't bother creating variables for them. |
When you run this program, the following output is displayed on the console:
T minus 20 T minus 19 T minus 18 T minus 17 T minus 16 Flood the pad! T minus 15 T minus 14 T minus 13 T minus 12 T minus 11 T minus 10 T minus 9 T minus 8 T minus 7 T minus 6 Start engines! T minus 5 T minus 4 T minus 3 T minus 2 T minus 1 Liftoff! T minus 0
As you can see, the LaunchEvent messages are interspersed with the CountDownClock messages. Thus, the launch events are triggered at the correct time.
Tip |
You can improve the main method for this class by using an ArrayList to store the Runnable objects. Then you can start all the LaunchEvent threads by using an enhanced for loop. Here's what the improved code looks like: public static void main(String[] args) { Thread clock = new CountDownClock(); ArrayList events = new ArrayList(); events.add(new LaunchEvent(16, "Flood the pad!")); events.add(new LaunchEvent(6, "Start engines!")); events.add(new LaunchEvent(0, "Liftoff!")); clock.start(); for (Runnable e : events) new Thread(e).start(); } |
The advantage of this technique is that you don't need to create a separate variable for each LaunchEvent.
Creating Threads That Work Together
Unfortunately, the countdown application presented in the previous section has a major deficiency. The CountDownClock and LaunchEvent threads depend strictly on timing to coordinate their activities. After these threads start, they run independently of one another. As a result, random variations in their timings can cause the thread behaviors to change.
For example, if you run the program several times in a row, you'll discover that sometimes the Start engines! message appears after the T minus 6 message, and sometimes it appears before the T minus 6 message. That might not be a big deal to you, but it would probably be disastrous for the astronauts on the shuttle.
What these classes really need is a way to communicate with each other. Listing 1-4 shows an improved version of the countdown application that incorporates several enhancements. The CountDownClock class in this version adds a new method named getTime that gets the current time in the countdown. Then, the LaunchEvent class checks the countdown time every 10 milliseconds and triggers the events only when the countdown clock actually says it's time. This version of the application runs consistently.
In addition, you want to enable the LaunchEvent class to monitor the status of the CountDownClock, but you don't want to couple the LaunchEvent and CountDownClock classes too closely together. For example, suppose you later develop a better countdown clock. If the LaunchEvent class knows what class is doing the counting, you have to recompile it if you use a different countdown class.
The solution is to use an interface as a buffer between the classes. This interface defines a method that gets the current status of the clock. Then, the CountDownClock class can implement this interface, and the LaunchEvent class can use any object that implements this interface to get the time.
Listing 1-4: The CountDown Application (Version 2)
import java.util.ArrayList; // version 2.0 of the Countdown application public class CountDownApp { public static void main(String[] args) { CountDownClock clock = new CountDownClock(20); → 8 ArrayList events = new ArrayList(); → 10 events.add(new LaunchEvent(16, → 12 "Flood the pad!", clock)); events.add(new LaunchEvent(6, "Start engines!", clock)); events.add(new LaunchEvent(0, "Liftoff!", clock)); clock.start(); → 19 for (Runnable e : events) → 21 new Thread(e).start(); } } interface TimeMonitor → 26 { int getTime(); } class CountDownClock extends Thread implements TimeMonitor → 31 { private int t; → 33 public CountDownClock(int start) → 35 { this.t = start; } public void run() { for (; t >= 0; t--) → 42 { System.out.println("T minus " + t); try { Thread.sleep(1000); } catch (InterruptedException e) {} } } public int getTime() → 54 { return t; } } class LaunchEvent implements Runnable → 60 { private int start; private String message; TimeMonitor tm; → 64 public LaunchEvent(int start, String message, TimeMonitor monitor) { this.start = start; this.message = message; this.tm = monitor; } public void run() { } boolean eventDone = false; while (!eventDone) { try { Thread.sleep(10); → 81 } catch (InterruptedException e) {} if (tm.getTime() <= start) → 85 { System.out.println(this.message); eventDone = true; } } }
The following paragraphs describe the high points of this version:
→ 8 |
As you see in line 35, the constructor for the CountDownClock class now accepts a parameter to specify the starting time for the countdown. As a result, this line specifies 20 as the starting time for the CountDownClock object. |
→ 10 |
An ArrayList of LaunchEvent objects is used to store each launch event. |
→ 12 |
The lines that create the LaunchEvent objects pass the CountDownClock object as a parameter to the LaunchEvent constructor. That way the LaunchEvent objects can call the clock's abort method if necessary. |
→ 19 |
The clock is started! |
→ 21 |
An enhanced for loop starts threads to run the LaunchEvent objects. |
→ 26 |
The TimeMonitor interface defines just one method, named getTime. This method returns an integer that represents the number of seconds left on the countdown timer. |
→ 31 |
The CountDownClock class now implements the TimeMonitor interface. |
→ 33 |
A private field named t is used to store the current value of the countdown clock. That way, the current clock value can be accessed by the constructor, the run method, and the getTime method. |
→ 35 |
The constructor for the CountDownClock class accepts the starting time for the countdown as a parameter. Thus, this countdown clock doesn't have to start at 20 seconds. The value passed via this parameter is saved in the t field. |
→ 42 |
The for loop in the run method tests and decrements the t variable. But because this variable is already initialized, it doesn't have an initialization expression. |
→ 54 |
The getTime() method simply returns the value of the t variable. |
→ 60 |
The start of the LaunchEvent class. |
→ 64 |
A private field of type TimeMonitor is used to access the count-down clock. A reference to this object is passed to the LaunchEvent class via its constructor. The constructor simply stores that reference in this field. |
→ 81 |
The while loop includes a call to Thread.sleep that sleeps for just 10 milliseconds. Thus, this loop checks the countdown clock every 10 milliseconds to see if its time has arrived. |
→ 85 |
This statement calls the getTime method of the countdown clock to see whether it's time to start the event. If so, a message is displayed, and eventDone is set to true to terminate the thread. |
Using an Executor
The countdown application in Listings 1-1 through 1-3 uses Java's original threading mechanisms-tools that were available in the mid-1990s, when Java was in diapers. Since then, Java programmers have longed for newer, more sophisticated threading techniques. The big breakthrough came in 2004, with the release of Java 1.5. The Java API gained a large assortment of classes for fine-grained control of the running of threads.
A full discussion of Java 1.5 threading would require another 850 pages. (How about Java 1.5 Threading All-in-One Desk Reference For Masochists?) This chapter presents only a small sampling of the newer Java threading features.
Listings 1-5 through 1-7 repeat the work done by Listings 1-1 through 1-3, but Listings 1-5 through 1-7 use Java 1.5 threading classes.
Listing 1-5: A New CountDownClock
public class CountDownClockNew implements Runnable { int t; public CountDownClockNew(int t) { this.t = t; } public void run() { System.out.println("T minus " + t); } }
Listing 1-6: A New Event Launcher
public class LaunchEventNew implements Runnable { private String message; public LaunchEventNew(String message) { this.message = message; } public void run() { System.out.println(message); } }
Listing 1-7: A New CountDown Application
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; class CountDownAppNew { public static void main(String[] args) { ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(25); Runnable flood, ignition, liftoff; flood = new LaunchEventNew("Flood the pad!"); ignition = new LaunchEventNew("Start engines!"); liftoff = new LaunchEventNew("Liftoff!"); for (int t = 20; t >= 0; t--) pool.schedule(new CountDownClockNew(t), (long) (20 - t), TimeUnit.SECONDS); pool.schedule(flood, 3L, TimeUnit.SECONDS); pool.schedule(ignition, 13L, TimeUnit.SECONDS); pool.schedule(liftoff, 19L, TimeUnit.SECONDS); pool.shutdown(); } }
In the new version of the countdown application, Listing 1-7 does all the busywork. The listing uses the ScheduledThreadPoolExecutor class. The class's long name tells much of the story:
- Scheduled: Using this class, you can schedule a run of code for some future time.
- ThreadPool: This class typically creates several threads (a pool of threads) all at once. When you want to run some code, you grab an available thread from the pool, and you use that thread to run your code.
- Executor: An Executor executes something. No big surprise here!
The loop in Listing 1-7 spawns 20 threads, each with its own initial delay. For example, the fifth loop iteration calls
pool.schedule(new CountDownClockNew(16), (long) (20 - 16), TimeUnit.SECONDS);
In the pool.schedule method call, the number (long) (20 - 16) tells Java to wait 4 seconds before running the T minus 16 thread. Each of the T minus threads has a different delay, so each thread runs at the appropriate time. The same is true of the flood, ignition, and liftoff events.
Synchronizing Methods
Whenever you work on a program that uses threads, you have to consider the nasty issue of concurrency. In particular, what if two threads try to access a method of an object at precisely the same time? Unless you program carefully, the result can be disastrous. A method that performs a simple calculation returns inaccurate results. In an online banking application, you might discover that some deposits are credited twice and some withdrawals aren't credited at all. In an online ordering system, one customer's order might get recorded in a different customer's account.
The key to handling concurrency issues is recognizing methods that update data and that might be called by more than one thread. After you identify those methods, the solution is simple: You just add the synchronized keyword to the method declaration, like this:
public synchronized void someMethod()...
This tells Java to place a lock on the object so that no other methods can call any other synchronized methods for the object until this method finishes. In other words, it temporarily disables multithreading for the object.
The next several listings present some concrete examples. Listing 1-8 creates an instance of the CountDownClock class (the class in Listing 1-1).
Then Listing 1-9 spawns two threads. Each thread runs a copy of the CountDownClock instance's code.
Listing 1-8: Creating Two CountDownClock Threads
import java.util.concurrent.ScheduledThreadPoolExecutor; public class DoTwoThings { ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(2); CountDownClock clock = new CountDownClock(); public static void main(String[] args) { new DoTwoThings(); } DoTwoThings() { pool.execute(clock); pool.execute(clock); pool.shutdown(); } }
The resulting output is a mishmash of two threads' outputs. The two threads execute their loops simultaneously-so after one thread displays its T minus 20, the other thread displays its own T minus 20. The same thing happens for T minus 19, T minus 18, and so on.
T minus 20 T minus 20 T minus 19 T minus 19 T minus 18 T minus 18 T minus 17 ... and so on, down to... T minus 1 T minus 1 T minus 0 T minus 0
Listing 1-9: Creating Two More CountDownClock Threads
import java.util.concurrent.ScheduledThreadPoolExecutor; public class DoTwoThingsSync { ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(2); CountDownClockSync clock = new CountDownClockSync(); public static void main(String[] args) { new DoTwoThingsSync(); } DoTwoThingsSync() { pool.execute(clock); pool.execute(clock); pool.shutdown(); } }
In Listing 1-10, Java's synchronized keyword ensures that only one thread at a time calls the run method. The resulting output shows one complete execution of the run method followed by another.
Listing 1-10: Using the synchronized Keyword
public class CountDownClockSync extends Thread { synchronized public void run() { for (int t = 20; t >= 0; t--) { System.out.println("T minus " + t); try { Thread.sleep(1000); } catch (InterruptedException e) {} } } }
The two threads' calls to the run method are not interleaved, so the output counts down from 20 to 0, and then counts down a second time from 20 to 0.
T minus 20 T minus 19 T minus 18 ... and so on, down to... T minus 2 T minus 1 T minus 0 T minus 20 T minus 19 T minus 18 ... and so on, down to... T minus 2 T minus 1 T minus 0
Warning |
The tough part is knowing which methods to synchronize. When I said that any method that updates data can be synchronized, I didn't just mean any method that updates a database. Any method that updates instance variables is at risk-and needs to be synchronized. That's because when two or more threads run a method at the same time, the threads share a common copy of the method's instance variables. |
TECHNICAL STAUFF |
Even methods that consist of just one line of code are at risk. For example, consider this method: int sequenceNumber = 0; public int getNextSequenceNumber() { return sequenceNumber++; } |
You'd think that because this method has just one statement, some other thread could not interrupt it in the middle. Alas, that's not the case. This method must get the value of the sequenceNumber field, add 1 to it, save the updated value back to the sequenceNumber field, and return the value. In fact, this single Java statement compiles to 11 bytecode instructions. If the thread is preempted between any of them by another thread calling the same method, the serial numbers get munged.
For safety's sake, why not just make all the methods synchronized? There are two reasons:
- It takes time to synchronize methods. Java has to acquire a lock on the object being synchronized, run the method, and then release the lock. But before it can do that, it has to check to make sure some other thread doesn't already have a lock on the object. All of this takes time.
- More importantly, synchronizing all your methods defeats the purpose of multithreading. So you should synchronize only those methods that require it.
REMEMBER |
The synchronized keyword doesn't block all access to an object. Other threads can still run unsynchronized methods of the object while the object is locked. |
TECHNICAL STAUFF |
The Object class provides three methods that can let synchronized objects coordinate their activities. The wait method puts a thread in the waiting state until some other thread calls either the object's notify or (more commonly) notifyAll method. These methods are useful in situations where one thread has to wait for another thread to do something before it can proceed. The classic example is a banking system where one thread makes withdrawals and the other makes deposits. If a customer's account drops to zero, the thread that makes withdrawals can call wait. Then, the thread that makes deposits can call notifyAll. That way, each time a deposit is made, the withdrawal thread can recheck the customer's account balance to see whether it now has enough money to make the withdrawal. |
Creating a Lock
With Java 1.5 came many new threading features. One such feature was the introduction of locks. A lock can take the place of Java's synchronized keyword, but a lock is much more versatile. Listings 1-11 and 1-12 illustrate the use of a lock.
Listing 1-11: Creating CountDownClock Threads (Again)
import java.util.concurrent.ScheduledThreadPoolExecutor; public class DoTwoThingsLocked { ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(2); CountDownClockLocked clock = new CountDownClockLocked(); public static void main(String[] args) { new DoTwoThingsLocked(); } DoTwoThingsLocked() { pool.execute(clock); pool.execute(clock); pool.shutdown(); } }
Listing 1-12: Using a Lock
import java.util.concurrent.locks.ReentrantLock; public class CountDownClockLocked extends Thread { ReentrantLock lock = new ReentrantLock(); public void run() { lock.lock(); for (int t = 20; t >= 0; t--) { System.out.println("T minus " + t); try { Thread.sleep(1000); } catch (InterruptedException e) {} } lock.unlock(); } }
Listing 1-12 is remarkably similar to Listing 1-10. The only significant difference is the replacement of the synchronized keyword by calls to ReentrantLock methods.
At the start of Listing 1-12, the code declares the variable lock-an instance of the ReentrantLock class. This lock object is like a gas station's restroom key. Only one thread at a time can have the lock object. When one thread gets the lock object-by calling lock.lock() at the start of the run method-then no other thread can get past the lock.lock() call. A second thread must wait at the lock.lock() call until the restroom key becomes available. In Listing 1-12, the restroom key becomes available only when the first thread reaches the lock.unlock() statement. After the first thread calls lock.unlock(), the second thread proceeds into the method's for loop.
The overall result is the same as the output of Listings 1-9 and 1-10. In this example, using a lock is no better than using Java's synchronized keyword. But Java 1.5 has several kinds of locks, and each kind of lock has its own useful features.
Threadus Interruptus
You can interrupt another thread by calling its interrupt method, provided you have a reference to the thread. For example:
t.interrupt();
Here the thread referenced by the t variable is interrupted. Now, all the interrupted thread has to do is find out that it has been interrupted and respond accordingly. That's the topic of the following sections.
Finding out whether you ve been interrupted
As you've already seen, several methods of the Thread class, including sleep and yield, throw InterruptedException. Up until now, I've told you to simply ignore this exception-and in many cases, that's appropriate. However, many (if not most) threads should respond to InterruptedException in one way or another. In most cases, the thread should terminate when it's interrupted.
Unfortunately, finding out if a thread has been interrupted isn't as easy as it sounds. InterruptedException is thrown when another thread calls the interrupt method on this thread while the thread is not executing. That's why the methods that can cause the thread to give up control to another thread throw this exception. That way, when the thread resumes execution, you know it was interrupted.
However, the yield and sleep methods aren't the only way for control to be wrested away from a thread. Sometimes the thread scheduler just steps in and says, "You've had enough time, now it's someone else's turn to play." If that happens, and then some other thread calls your thread's interrupt method, InterruptedException isn't thrown. Instead, a special flag called the interrupted flag is set to indicate that the thread was interrupted. You can test the status of this flag by calling the static interrupted method.
Unfortunately, that means your threads have to check twice to see whether they have been interrupted. The usual way to do that is to follow this form:
public void run() { boolean done = false boolean abort = false; while(!done) { // do the thread's work here // set done to true when finished try { sleep(100); // sleep a bit } catch(InterruptedException e) { abort = true; } if (Thread.interrupted()) abort = true; if (abort) break; } }
Here the abort boolean variable is set to true if InterruptedException is thrown or if the interrupted flag is set. Then, if abort has been set to true, a break statement is executed to leave the while loop. Of course, this scheme has a million variations. But this one works in most situations.
Aborting the countdown
To illustrate how you can interrupt threads, Listing 1-13 shows yet another version of the countdown application. This version aborts the countdown if something goes wrong with any of the launch events.
To simplify the code a bit, I assume that things aren't going well at NASA, so every launch event results in a failure that indicates to abort the countdown. Thus, whenever the start time for a LaunchEvent arrives, the LaunchEvent class attempts to abort the countdown. It goes without saying that in a real launch-control program, you wouldn't want to abort the launch unless something actually does go wrong.
Listing 1-13: The Countdown Application with Aborts
import java.util.ArrayList; public class CountDownApp → 3 { public static void main(String[] args) { CountDownClock clock = new CountDownClock(20); ArrayList events = new ArrayList(); events.add(new LaunchEvent(16, "Flood the pad!", clock)); events.add(new LaunchEvent(6, "Start engines!", clock)); events.add(new LaunchEvent(0, "Liftoff!", clock)); clock.start(); for (Runnable e : events) new Thread(e).start(); } } interface TimeMonitor { int getTime(); void abortCountDown(); → 26 } class CountDownClock extends Thread implements TimeMonitor { private int t; public CountDownClock(int start) { this.t = start; } public void run() { boolean aborted = false; → 40 for (; t >= 0; t--) { System.out.println("T minus " + t); try { Thread.sleep(1000); } catch (InterruptedException e) { aborted = true; → 50 } if (Thread.interrupted()) aborted = true; → 53 if (aborted) → 54 { System.out.println( "Stopping the clock!"); break; } } } public int getTime() { return t; } public synchronized void abortCountDown() → 67 { Thread[] threads = new Thread[Thread.activeCount()]; → 69 Thread.enumerate(threads); → 70 for(Thread t : threads) → 71 t.interrupt(); } } class LaunchEvent implements Runnable { private int start; private String message; TimeMonitor tm; public LaunchEvent(int start, String message, TimeMonitor monitor) { this.start = start; this.message = message; this.tm = monitor; } public void run() { boolean eventDone = false; boolean aborted = false; → 92 while (!eventDone) { try { Thread.sleep(10); } catch (InterruptedException e) { aborted = true; → 101 } if (tm.getTime() <= start) { System.out.println(this.message); eventDone = true; System.out.println("ABORT!!!!"); → 107 tm.abortCountDown(); → 108 } if (Thread.interrupted()) aborted = true; → 111 if (aborted) → 112 { System.out.println( "Aborting " + message); break; } } } }
The following paragraphs point out the highlights of this program:
→ 3 |
The CountDownApp class itself hasn't changed. That's the beauty of object-oriented programming. Although I changed the implementations of the CountDownClock and LaunchEvent classes, I didn't change the public interfaces for these classes. As a result, no changes are needed in the CountDownApp class. |
→ 26 |
The LaunchEvent class needs a way to notify the CountDownTimer class that the countdown should be aborted. To do that, I added an abortCountDown method to the TimeMonitor interface. |
→ 40 |
The run method of the CountDownClass uses a boolean variable named aborted to indicate whether the thread has been interrupted. This variable is set to true in line 50 if InterruptedException is caught. It's also set to true in line 53 if Thread.interrupted() returns true. |
→ 54 |
If the aborted field has been set to true, it means the thread has been interrupted. So the message Stopping the clock! is displayed, and a break statement exits the loop. Thus, the thread is terminated. |
→ 67 |
The abortCountDown method is synchronized. That's because any of the LaunchEvent objects can call it, and there's no guarantee that they won't all try to call it at the same time. |
→ 69 |
The abortCountDown method starts by creating an array of Thread objects that's large enough to hold all the active threads. The number of active threads is provided by the activeCount method of the Thread class. |
→ 70 |
The abortCountDown method then calls the enumerate method of the Thread class to copy all the active threads into this array. Note that this method is static, so you don't need a reference to any particular thread to use it. (The activeCount method used in line 69 is static too.) |
→ 71 |
An enhanced for loop is used to call the interrupt method on all the active threads. That shuts down everything. |
→ 92 |
Like the CountDownClock class, the LaunchEvent class uses a boolean variable to indicate whether the thread has been interrupted. This thread is set if InterruptedException is caught in line 101 or if Thread.interrupted() returns true in line 111. Then, it's tested in line 112. If it has been set to true, the thread prints a message indicating that the launch event has been aborted, and a break statement is used to exit the loop and, therefore, terminate the thread. |
→ 101 |
When the launch event's start time arrives, the LaunchEvent class displays the message ABORT! and calls the abortCountDown method. Thus, the countdown is aborted the first time any launch event occurs. |
When you run this version of the countdown application, here's what appears on the console:
T minus 20 T minus 19 T minus 18 T minus 17 T minus 16 Flood the pad! ABORT!!!! Stopping the clock! Aborting Flood the pad! Aborting Start engines! Aborting Liftoff!