Site Search:

Chapter 7 Cancellation and Shutdown

Back>

7.1 Task cancellation

An activity is cancellable if external code can move it to completion before its normal completion.
Java use cooperative mechanisms to cancel runnable and task. One way is setting a "cancellation requested" flag that the task checks periodically.

Example

7.1.1 Interruption

Using flag to cancel task assumes that the running thread will periodically check the flag. This assumption won't always hold for code that calls blocking method such as Thread.sleep or BlockingQueue.put.

Example

Interruption is usually the most sensible way to implement cancellation.

Example

Calling interruption does not necessarily stop the target from doing what is doing; it merely delivers the message that interruption has been requested.

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.

7.1.2 Interruption Policies

The most sensible interruption policy is some form of thread-level or service-level cancellation: exit as quickly as practical, cleaning up if necessary, and possibly notifying some owning entity that the thread is exiting.

Tasks do not execute in threads they own; they borrow threads owned by a service such as a thread pool. Code that doesn't own the thread should be careful to preserve the interrupted status so that the owning code can eventually act on it, even if the "guest" code acts on the interruption as well.

Because each thread has its own interruption policy, you should not interrupt a thread unless you know what interruption means to that thread.

7.1.3 Responding to interruption

Only code that implements a thread's interruption policy may swallow an interruption request. General-purpose task and library code should never swallow interrupted requests.

Activities that do not support cancellation but still call interruptible blocking methods will have to call them in a loop, retrying when interruption is detected. In this case, they should save the interruption status locally and restore it just before returning, as shown in the following example.

Example

7.1.4 Example, timed run

You should know a thread's interruption policy before interrupting it. If your Runnable or Task can be called from an arbitrary thread, it cannot know the calling thread's interruption policy. Interrupting a thread pool thread within a submitted Runnable is dangerous, it could end up interrupted a totally irrelevant Runnable submitted later as shown in the following example.

Example

Interrupting a thread pool thread within a submitted Runnable is never a good idea, however, if you are stubborn and clever enough, you can interrupt any new thread created within the submitted Runnable, since you know the new thread's interruption policy, as shown in the following example:

Example

7.1.5 Cancellation via Future

You should not interrupt a thread pool thread directly because you don't know its cancelation policy. You should cancel tasks through their Future provided by a standard Executor, knowing that the task execution threads are created by the standard Executor implementations, and the Executor encapsulates the knowledge of cancellation policy in the Future.cancel.

You job as developer is simple, you work above interfaces:

Example

When Future.get throws InterruptedException or TimeoutException and you know that the result is no longer needed by the program, cancel the task with Future.cancel.


7.1.6 Dealing with non-interruptible blocking

Not all blocking library methods respond to interruption; if a thread is blocked performing a synchronous socket I/O, interruption has no effect other than setting the thread's interrupted status. Take synchronous socket I/O in java.io for example, read and write methods in InputStream and OutputStream are not responsive to interruption, but closing the underlying socket makes any threads blocked in read or write throw a SocketException.

Example

7.1.7 Encapsulating nonstandard cancellation with newTaskFor


Example


7.2 Stopping a thread-based service

Provide lifecycle methods whenever a thread-owning service has a lifetime longer than that of the method that created it.

ExecutorService provides the lifecycle methods shutdown and shutdownNow to shut itself down that also shut down the owned threads.

7.2.1 Example: a logging service


Stream classes like PrintWriter are thread-safe, so use println statements as logging don't need extra synchronization. This simple approach has performance issues. A better way is to move the logging activity to a separate logger thread. The Producers hand the log messages off to the Logger thread via BlockingQueue and the logger thread writes it out.

Example

The above Example LogWriter.java has race conditions that prevent Producer threads to exit blocking state, it also lost the last a few messages in the blocking queue when the reader thread is interrupted. The following example, LogService.java fixed the two problems with proper synchronization between Producer threads and logger thread.

Example

7.2.2 ExecutorService shutdown

We don't have to write the synchronized code like 7.2.1 examples in order to coordinate the Producer and Consumer threads, ExecutorService gives high quality thread management for free.

In the following example, we achieve the same logging goal by delegating the thread lifecycle management --  start, run, stop to an ExecutorService.

Example

7.2.3 Poison pills

Another way to stop a producer-consumer based service is to put a "poison pill" -- producer put an object on the queue then stops submitting any new object to the queue; once the consumer gets it, consumer knows it is time to stop.

Example

7.2.4 Example: a one-shot execution service

If 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 whose lifetime is bounded by that method.

Example

7.2.5 Limitations of shutdownNow

ExecutorService can call shutdown or shutdownNow. The later is faster.

  • shutdownNow attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution.
  • shutdown initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted. Invocation has no additional effect if already shut down.
Event though shutdownNow returns a list of the tasks that were awaiting execution, it didn't say what state these tasks are. Are they started but did not complete or they haven't get started? The following code shows a technique for determining which tasks were in progress at shutdown time.


Example

7.3 Handling abnormal thread termination


7.4 JVM shutdown