Chapter 1
IN THIS CHAPTER
Examining threads
Creating threads from the Thread class
Working with the Runnable interface
Creating threads that cooperate
Executing threads
Making methods cooperate
Using a lock
Interrupting threads
Have you ever seen a plate-spinning act, in which a performer spins plates or bowls on top of poles, keeping multiple plates spinning at the same time, running from pole to pole to give each plate a little nudge — just enough to keep it going? The world record is 108 plates kept simultaneously spinning.
In Java, threads are the equivalent of plate spinning. Threads let you divide the work of an application into separate pieces, all of which then run simultaneously. The result is a faster and more efficient program, but along with the increased speed come 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 understand. In this chapter, I focus on those basics and leave the advanced techniques for the grad students.
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 — but Java lets you create programs that start additional threads to perform specific tasks.
You’re probably familiar with programs that use threads to perform several tasks at the same time. Here are some common examples:
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 6.
Suppose you’re developing software for NASA, and you’re in charge of the program that controls the final 20 seconds of the countdown for a manned spacecraft. Your software has to coordinate several key events that occur when the clock reaches certain points:
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 1-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. We’ll start by looking at the Thread
class.
The Thread
class lets you create an object that can be run as a thread in a multithreaded Java application. The Thread
class has quite a few constructors and methods, but for most applications, you need to use only 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.)
TABLE 1-1 Constructors and Methods of the Thread Class
Constructor |
Explanation |
|
Creates an instance of the |
|
Creates a |
|
Turns any object that implements an API interface called |
|
Creates a thread from any object that implements |
|
Returns the number of active threads. |
|
Fills the specified array with a copy of each active thread. The return value is the number of threads added to the array. |
|
Returns the name of the thread. |
|
Returns the thread’s priority. |
|
Interrupts this thread. |
|
Checks whether the thread has been interrupted. |
|
Sets the thread’s priority. |
|
Sets the thread’s name. |
|
Causes the currently executing thread ( |
|
Is called when the thread is started. Place the code that you want the thread to execute inside this method. |
|
Starts the thread. |
|
Causes the currently executing thread to yield to other threads that are waiting to execute. |
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.
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:
CountDownClock
class extends the Thread
class. Thread
is defined in the java.lang
package, so you don’t have to provide an import
statement to use it.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.run
method includes a for
loop that counts down from 20 to 0.CountDownClock
class uses the sleep
method to pause for 1 second. Because the sleep
method throws Interrupted Exception
, a try/catch statement handles this exception. If the exception is caught, it is simply ignored.After you define a class that defines a Thread
object, you can create and start the thread. 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 Count DownClock
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 1-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.
For the threads that trigger specific countdown events such as flooding the launch pad, starting the events, and 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.)
To use the Runnable
interface to create and start a thread, you have to do the following:
Runnable
.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 and 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 in 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 code compressed to something more like
Thread t = new Thread(new RunnableClass());
t.start();
or even just this:
new Thread(new RunnableClass()).start();
This single-line version works — provided that you don’t need to access the thread object later in the program.
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. 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:
Runnable
interface.run
method, the Thread.sleep
method is called to put the thread to sleep until the desired countdown time arrives. The length of time that 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.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 spacecraft.
LISTING 1-3 The CountDownApp Class (Version 2)
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:
main
method starts by creating an instance of the Count DownClock
class and saving it in the clock
variable.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
.clock
thread is started. The countdown starts ticking.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 after they’re started, it doesn’t bother creating variables for them.When you run this program, output similar to the following 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 times.
Note that the exact order in which some of the messages appear may vary slightly. For example, "Flood the pad!"
might sometimes come before "T minus 16"
because of slight variations in the precise timing of these independently operating threads. Later in this chapter, the section “Creating Threads That Work Together” shows you how to avoid such inconsistencies.
public static void main(String[] args)
{
Thread clock = new CountDownClock();
ArrayList<Runnable> events
= new ArrayList<Runnable>();
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
. (Don’t forget to add an import
statement for the java.util.*
to gain access to the ArrayList
class.)
Unfortunately, the countdown application presented in the preceding 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. 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 seem like a big deal to you, but it probably would be disastrous for the astronauts on the spacecraft. What these classes really need is a way to communicate.
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 that 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. Suppose that later, you 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 Coordinated CountDown Application
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<Runnable> events =
new ArrayList<Runnable>();→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→32
{
private int t;→34
public CountDownClock(int start)→36
{
this.t = start;
}
public void run()
{
for (; t >= 0; t--)→43
{
System.out.println("T minus " + t);
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{}
}
}
public int getTime()→55
{
return t;
}
}
class LaunchEvent implements Runnable→61
{
private int start;
private String message;
TimeMonitor tm;→65
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);→82
}
catch (InterruptedException e)
{}
if (tm.getTime() <= start)→86
{
System.out.println(this.message);
eventDone = true;
}
}
}
}
The following paragraphs describe the high points of this version:
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.ArrayList
of LaunchEvent
objects is used to store each launch event.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.for
loop starts threads to run the LaunchEvent
objects.TimeMonitor
interface defines just one method, named getTime
. This method returns an integer that represents the number of seconds left on the countdown timer.CountDownClock
class implements the TimeMonitor
interface.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.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.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.getTime()
method simply returns the value of the t
variable.LaunchEvent
class.TimeMonitor
is used to access the countdown clock. A reference to this object is passed to the LaunchEvent
class via its constructor. The constructor simply stores that reference in this field.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 whether its time has arrived.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.The countdown application in Listings 1-1 through 1-4 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. Since then, subsequent releases of Java have added even more classes to give you even better control over how threads execute.
A full discussion of Java threading would require another 850 pages. (How about Java Threading All-in-One For Masochists?) This chapter presents only a small sampling of these Java threading features.
Listings 1-5 through 1-7 repeat the work done by Listings 1-1 through 1-4, 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) at the same time. When you want to run some code, you grab an available thread from the pool and 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. The fifth loop iteration, for example, 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 scheduling 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.
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 code 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. (I discuss locking in the section “Creating a Lock,” later in this chapter.)
The next several listings present some concrete examples. Listing 1-8 creates an instance of the CountDownClock
class (the class in Listing 1-1).
LISTING 1-8 Creating Two CountDownClock Threads
import java.util.concurrent.ScheduledThreadPoolExecutor;
public class DoTwoThings
{
ScheduledThreadPoolExecutor pool =
new ScheduledThreadPoolExecutor(2);
CountDownClock clock = new CountDownClock(20);
public static void main(String[] args)
{
new DoTwoThings();
}
DoTwoThings()
{
pool.execute(clock);
pool.execute(clock);
pool.shutdown();
}
}
The resulting output is an unpredictable mishmash of two threads’ outputs, with some of the counts duplicated and others skipped altogether, like this:
T minus 20
T minus 20
T minus 19
T minus 19
T minus 18
T minus 17
T minus 16
T minus 15
T minus 13
T minus 13
T minus 12
T minus 12
T minus 11
T minus 11
T minus 10
T minus 9
T minus 7
T minus 7
T minus 6
T minus 5
T minus 4
T minus 3
T minus 2
T minus 2
T minus 1
T minus 0
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.
Then Listing 1-9 spawns two threads, each of which runs a copy of the CountDownClock
instance’s code.
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(20);
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
class CountDownClockSync extends Thread
{
private int start;
public CountDownClockSync(int start)
{
this.start = start;
}
synchronized public void run()
{
for (int t = start; 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
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 those bytecodes by another thread calling the same method, the serial numbers get munged.
For safety’s sake, why not just make all the methods synchronized? You have two reasons not to do so:
A few years back, Java version 1.5 introduced 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 — 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 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.
You can interrupt another thread by calling its interrupt
method, provided that you have a reference to the thread, as in this 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.
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. Many (if not most) threads, however, should respond to InterruptedException
in one way or another. In most cases, the thread should terminate when it’s interrupted.
Unfortunately, finding out whether 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 that it was interrupted.
The yield
and sleep
methods aren’t the only way for control to be wrested away from a thread, however. 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 boolean
variable abort
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. This scheme has a million variations, of course, but this one works in most situations.
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 a need 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<Runnable> events =
new ArrayList<Runnable>();
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();→25
}
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()→68
{
Thread[] threads =
new Thread[Thread.activeCount()];→71
Thread.enumerate(threads);→72
for(Thread t : threads)→73
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;→95
while (!eventDone)
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
aborted = true;→104
}
if (tm.getTime() <= start)
{
System.out.println(this.message);
eventDone = true;
System.out.println("ABORT!!!!");
tm.abortCountDown();
}
if (Thread.interrupted())
aborted = true;→114
if (aborted)→115
{
System.out.println(
"Aborting " + message);
break;
}
}
}
}
The following paragraphs point out the highlights of this program:
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.LaunchEvent
class needs a way to notify the CountDown Timer
class that the countdown should be aborted. To do that, I added an abortCountDown
method to the TimeMonitor
interface.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
.aborted
field has been set to true
, it means that 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.abortCountDown
method is synchronized. That happens 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.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.abortCountDown
method 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.)for
loop is used to call the interrupt
method on all the active threads. That method shuts down everything.CountDownClock
class, the LaunchEvent
class uses a boolean
variable to indicate whether the thread has been interrupted. This variable is set if InterruptedException
is caught in line 104 or if Thread.interrupted()
returns true
in line 114; then it’s tested in line 115. If the aborted
variable 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.When you run this version of the countdown application, the console output will appear something like this (minor variations might occur because of the synchronization of the threads):
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!