It is easy to start tasks and threads. Most of the time we allow them to decide when to stop by letting them run to completion. Sometimes, however, we want to stop tasks or threads earlier than they would on their own, perhaps because the user cancelled an operation or the application needs to shut down quickly.
Java does not provide any mechanism for safely forcing a thread to stop what it is doing. Instead, it provides interruption, a cooperative mechanism that lets one thread ask another to stop what it is doing.
The cooperative approach is required because we rarely want a task, thread, or service to stop immediately, since that could leave shared data structures in an inconsistent state.
Dealing well with failure, shutdown, and cancellation is one of the characteristics that distinguishes a well-behaved application from one that merely works.
An activity is cancellable if external code cn move it to completion before its normal completion. There are a number of reasons why you might want to cancel an activity:
- User-requested cancellation
- Time-limited activities
- Application events
One of the cancelling mechanism is setting a “cancellation requested” flag that the tasks checks periodically:
And below code shows a sample use of this class that lets the prime generator run for one second before cancelling it.
A task that wants to be cancellable must have a cancellation policy that specifies the “how”, “when”, and “what” of cancellation - how other code can request cancellation, when the task checks whether cancellation has been requested, and what actions the task takes in response to cancellation request.
If a task uses previous approach to call a blocking method such as
BlockingQueue.put, we could have a more serious problem - the task might never check the cancellation flag and therefore might never terminate.
There is nothing in the API or language specification that ties interruption to any specific cancellation semantics, but in practice, using interruption for anything but cancellation is fragile and difficult to sustain in larger applications.
Each thread has a boolean interrupted status; interrupting a thread sets its interrupted status to true. Blocking library methods like
Object.wait try to detect when a thread has been interrupted and return early. They respond to interruption by clearing the interrupted status and throwing
InterruptedException, indicating the blocking operation completed early due to interruption.
If a thread is interrupted when it is not blocked, its interrupted status is set, and it is up to the activity being cancelled to poll the interrupted status to detect interruption. In this way interruption is “sticky” - if it doesn’t trigger an
InterruptedException, evidence and interruption persists until someone deliberately clears the interrupted status.
interrupted does not necessarilly stop the target thread from doing what it is doing, it merely delievers the message that interrutption has been requested
A good way to think about interruption is that it does not actually interrupt a running thread; it just requests that the thread interrupt itself at the next convenient opportunity. (These opportunities are called cancellation points)
Well behaved methods may totally ignore such requests so long as they leave the interruption request in place so that calling code can do something with it. Poorly behaved methods swallow the interrupt request, thus denying code further up the call stack the opportunity to act on it.
If you call
interrupted and it returns
true, unless you are planning to swallow the exception, you should do something with it - either throw
IntterruptedException or restore the interrupted status by calling
If you code your tasks to be responsive to interruption, you can use interruption as your cancellation mechanism and take advantage of the interruption support provided by many library classes.
An interruption policy determines how a thread interprets an interruption request - what it does when one is detected, what units of work are considered atomic with respect to interruption, and how quickly it reacts to interruption.
It’s important to distinguish between how tasks and threads should react to interruption. A single interrupt request may have more than one desired recipient - interrupting a worker thread in a thread pool can mean both “cancel the current task” and “shut down the worker thread”
A task should not assume anything about the interruption policy of its executing thread unless it is explicitly designed to run within a service that has a specific interruption policy. Whether a task interprets interruption as cancellation or takes some other action on interruption, it should take care to preserve the executing thread’s interruption status. If it is not simply going to propagate
InterruptedException to its caller, it should restore the interruption status after catching
Because each thread has its own interruption policy, you should not interrupt a thread unless you know what interruption means to that thread
When you call an interruptible blocking method such as
BlockingQueue.put, there are two practical strategies for handling
- Propagate the exception (possibly after some task-specific cleanup), making your method an interruptible blocking method, too; or
- Restore the interruption status so that code higher up on the call stack can deal with it
If you don’t want to or cannot propagate
InterruptedException, you need to find another way to preserve the interruption request. The standard way to do this is to restore the interrupted status by calling
interrupt again. What you should not do is swallow the
InterruptedException by catching it and doing nothing in the
catch block, unless your code is actually implementing the interruption policy for a thread.
If your code does not call interruptible blocking methods, it can still be made responsive to interruption by polling the current thread’s interrupted status throughout the task code. Choosing a polling frequency is a tradeoff between efficiency and responsiveness.
Cancellation can invove state other than the interruption status; interruption can be used to get the thread’s attention, and information stored elsewhere by the interrupting thread can be used to provide further instructions for the interrupted thread.
Many problems can take forever to solve, for others, the answer might be found reasonably quickly but also might take forever. Being able to say “spend up to ten minutes looking for the answer” or “enumerate all the answers you can in ten minutes” can be useful in these situations.
Code above shows an attempt at running an arbitrary
Runnable for a give namount of time. It runs the tassk in the calling thread and schedules a cancellation task to interrupt it after a given time interval.
This is an appealingly simple approach, but it violates the rules: you should know a thread’s interruption policy before interrupting it. Since
timeRun can be called from an arbitrary thread, it cannot know the calling thread’s interruption policy. If the task completes before the timeout, the cancellation task that interrupts the thread in which
timeRun was called could go off after
timedRun has returned to its caller, which has an undefined behavior.
Futher, if the task is not responsive to the interruption,
timedRun will not return until the task finishes, which may be long after the desired timeout.
The above code addresses the problems in the previous examples, but because it relies on a timed
join, it shares a deficiency with
join: we don’t know if control was returned because the thread exited normally or because the
join timed out.
ExecutorService.submit returns a
Future describing the task.
Future has a
cancel method that takes a boolean argument,
mayInterruptIfRunning, and returns a value indicating whether the cancellation attempt was successful.
TimeoutException and you know that result is no longer needed by the program, cancel the task with
Many blocking library methods respond to interruption by returning early and throwing
InterruptedException, which makes it easier to build tasks that are responsive to cancellation.
However, not all blocking methods or blocking mechanisms are responsive to interruption; if a thread is blocked performing synchronous socket I/O or waiting to acquire an intrinsic lock, interruption has no effect other than setting thread’s interrupted status.
- Synchronous socket I/O in
- Synchronous socket I/O in
- Asyncrhonous I/O with
- Lock acquisition
ReaderThread shows a technique for encapsulating nonstandard cancellation.
ReaderThread manages a single socket connection, reading synchronously from the socket and passing any data received to
processBuffer. To facilitate terminating a user connection or shutting down the server,
interrupt to both deliver a standard interrupt and close the underlying socket; thus interrupting a
ReaderThread makes it stop what it is doing whether it is blocked in
read or in an interruptible blocking thread.
The technique used in
ReaderThread to encapsulate nonstandard cancellation can be refined using the
newTaskFor hook added to
ThreadPoolExecutor in Java 6.
Callable is submitted to an
submit returns a
Future that can be used to cancel the task. The
newTaskFor hook is a factory method that creates the
Future representing the task.
Applications commonly create services that own threads, such as thread pools, and the lifetime of these servicea is usually longer than that of the method that creates them. If the application is to shut down gracefully, the threads owned by these services need to be terminated. Since there is no preemptive way to stop a thread, they must instead be persuaded to shut down on their own.
Provide lifecycle methods whenever a thread-owning service has a lifetime longer than that of the method that created it.
Inline logging can have some performance costs in high volume applications, so another alternative is to have the
log call queue the log message for processing by another thread.
LogWriter shows a simple logging service in which the logging activity is moved to a separate logger instead. Instead of having the thread that produces the message write it directly to the output stream,
LogWriter hands it off to the logger thread via a
BlockingQueue and the logger thread writes it out.
For a service like
LoggerWrite to be useful in production, we need a way to terminate the logger thread so it does not prevent the JVM from shutting down normally. However, simply making the logger thread exit is not a very satisfying shudown mechanism. Such a abrupt shutdown discards log messages that might be waiting to be written to the log, but, more importantly, threads blocked in
log because the queue is full will never become unblocked.
Cancelling a producer-consumer activity requires cancelling both the producers and the consumers. Interrupting the logger thread deals with the consumer, but because the producers in this case are not dedicated threads, cancelling them is harder.
ExecutorService offers two ways to shut down: graceful shutdown with
shutdown, and abrupt shutdown with
shutdownNow. In an abrupt shutdown,
shutdownNow returns the list of tasks that had not yet started after attempting to cancel all actively executing tasks.
The two different termination options after a tradeoff between safety and responsiveness: abrupt termination is faster but risker because tasks may be interrupted in the middle of execution, and normal termination is slower but safer because the
ExecutorService does not shut down until all queued tasks are processed.
Another way to convince a producer-sonsumer service to shut down is with a poison pill: a recognizable object placed on the queue that means “when you get this, stop”.
IndexingService shows a single-producer, single-consumer version of the desktop search example that uses a poison pill to shut down the service.
Poison pills work only when the number of producers and consumers is known.
In a method needs to process a batch of tasks and does not return until all the tasks are finished, it can simplify service lifecycle management by using a private
Executor service whose lifetime is bounded by that method. (The
invokeAny methods can often be useful in such situations)
checkMail method checks for new mail in parallel on a number of hosts. It creates a private executor and submits a task for each host: it then shut down the executor and waits for termination, which occurs when all the mail-checking tasks have completed.
ExecutorService is shut down abruptly with
shutdownNow, it attempts to cancel the tasks currently in progress and returns a lit of tasks that were submitted but never started so that they can be logged or saved for later processing.
However, there is no general way to find out which tasks started but did not complete. This means there is no way of knowing the state of the tasks in progress at shutdown time unless the tasks themeselves perform some sort of checkpointing.
TrackingExecutor shows a technique for determining which tasks were in progress at shutdown time. By encapsulating an
ExecutorService and instrumenting
execute (and similarly
submit, not shown) to remember which tasks were cancelled after shutdown,
TrackingExecutor can identify which tasks started but did not complete normally. In order for this technique to work, the tasks must preserve the thread’s interrupted status when they return.
WebCrawler shows an application of
TrackingExecutor. The work of a web crawler is often unbounded, so if a crawler must be shutdown we might want to save its state so it can be restarted later.
Failure of a thread in a concurrent application is not always so obvious. Also, when a thread fails, the application may appear to continue to work, so its failure could go unnoticed.
The consequences of abnormal thread death range from benign to disatrous. (Thread pool vs. single-threaded GUI application)
Code above illustrates a way to structure a worker thread within a thread pool. If a task throws an unchecked exception, it allows the thread to die, but not before notifying the framework that the thread has died.
The Thread API provides the
UncaughtExceptionHandler facility, which lets you detect when a thread dies due to an uncaught exception.
When a thread exits due to an uncaught exception, the JVM reports this event to an application-provided
UncaughtExceptionHandler; if no handler exists, the default behavior is to print the stack trace to
In long-running applications, always use uncaught exception handler for all threads that at least log the exception.
To set an
UncaughtExceptionHandler for poor threads, provide a
ThreadFactory to the
ThreadPoolExecutor construtor. The standard thread pools allow an uncaught task exception to terminate the pool thread, but use a
try-finally block to be notified when this happens so the thread can be replaced. Withoug an uncaught exception handler or other failure notification mechanism, tasks can appear to fail silently, which can be very confusing.
Somewhat confusingly, exceptions thrown from tasks make it to the uncaught exception handler only for tasks submitted with
execute; for tasks submitted with
submit, any thrown exception, checked or not, is considered to be part of the task’s return status.
The JVM can shut down in either an orderly or abrupt manner. An orderly shutdown is initiated when the last “normal” (nondaemon) threads terminates, someone calls
System.exit, or by other platform-specific means (such as sending a
SIGINT or hitting
CTRL-C). While this is the standard and preferred way for the JVM to shut down, it can also be shut down abruptly by calling
Runtime.halt or by killing the JVM process through the operating system (e.g. sending a
In an orderly shutdown, the JVM first starts all registered shutdown hooks. If all shutdown hooks have completed, the JVM may choose to run finalizers if
true. If the shutdown hooks or finalizers don’t complete, then JVM must be shut down abruptly.
Sometimes you want to create a thread that performs some helper function but you don’t want the existence of this thread to prevent the JVM from shutting down. This is what daemon threads are for.
Threads are divided into two types: normal threads and daemon threads. When the JVM starts up, all the threads it creates (e.g. GC and housekeeping threads) are daemon threads, except the main thread. When a new thread is created, it inherits the daemon status of the thread that created it.
Daemon threads are not a good substitute for properly managing the lifecycle of services within an application.
To assist in releasing resources, GC treats objects that have a nontrivial
finalize method specially: after they are reclaimed by the collector,
finalize is called so that persistent resources can be released.
Since finalizers can run in a thread managed by the JVM, any state accessed by a finalizer will be accessed by more than one thread and therefor must be accessed with synchronization. Also finalizers offer no guaranttees on when or even if they run, and they impose a significant performance cost on objects with nontrivial finalizers.
The sole exception is when you need to manage object that hold resources acquired by native methods.
End-of-lifecycle issues for tasks, threads, services, and applications can add complexity to their design and implementation. Java does not provide a preemptive mechanism for cancelling activities or terminating threads. Instead, it provides a cooperative interruption mechanism that can be used to facilitate cancellation, but it is up to you to consstruct protocols for cancellation and use them consistently. Using
FutureTask and the
Executor framework simplifies building cancellable tasks and services.
(To Be Continued)