Chapter 7 - Cancellation & Shutdown

Java does not provide any mechanism for safely forcing a thread to stop what it is doing. Instead, we need to rely on interruption where a thread can request another thread to stop. The thread to be stopped may choose to ignore the request or can terminate after optionally performing cleanup operations.

Don't use the deprecated Thread.stop() and suspend() methods.

A task is cancelable if some external code can move it to a completed state before its normal completion. A task that supports cancellation must specify its cancellation policy:

how external code can request cancellation

responsiveness guarantees to cancellation requests

what happens on cancellation (such as cleanup operations)

One cooperative mechanism for terminating a task is to use a cancellation requested flag which the task periodically checks. If the flag is set by some external code, then the task terminates early. Remember to make the cancellation requested flag volatile. Otherwise changes made by the external code may never become visible to the task.

The thread will exit only when it checks the cancellation flag. Hence there is no guarantee on if and when the check will be made.

Can take a very long time to take effect (if at all) if the thread to be cancelled is stuck at a blocking operation.

Interruption Is usually the most sensible way to implement cancellation

Each thread has a boolean interrupted status, which is set to true when a thread is interrupted.

interrupt() interrupts the target thread

isInterrupted() returns the interrupted status of the target thread

interrupted() clears the interrupted status of the current thread and returns its previous value. This is the only way to clear the interrupted status of a thread. Poor choice of function name.

Blocking library calls try to detect when a thread has been interrupted and return early. They clear the interrupted status and throw an InterruptedException. There is no guarantee about how quickly a blocking method will detect interruption. In practice, it happens fairly quickly. When a thread that is not blocked is interrupted, its interrupted status is set. It is upto the activity being cancelled to poll the interrupted status and respond appropriately.

A task does not need to immediately stop on detecting the interrupted status. It can postpone acting on the interruption till a more opportune moment. This can prevent internal data structures from being corrupted when interrupted in the middle of some critical operations.

There is a distinction between how tasks and threads react to interruption. An interrupt on a worker thread in a thread pool can cancel the current task as well as shut down the worker thread. Hence, guesT code that doesn't own a thread must preserve the interrupted status of the thread after acting on the interrupt, so that the owner can appropriately deal with it later.

A thread should be interrupted only by its owner. Because each thread has its own interruption policy, you should not interrupt a thread unless you know what interruption means to that thread.

Responding to interruption

Propagate the interruptedException, OR

Restore the interrupted status by calling Thread.currentThread.interrupt(). This is the only feasible solution in cases like Runnable.run() which does not allow exceptions to be thrown.

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

Activities that do not support cancellation but still call interruptible blocking methods must call them in a loop, retrying when interruption is detected. The interruption status should be saved locally and restored before returning. Restoring the interrupted status immediately can result in an infinite loop.

Cancellation via Future

Future.cancel(boolean mayInterruptIfRunning) - if mayInterruptIfRunning is true and the task is running in some thread, then that thread is interrupted. If false, cancel() only means that don't run this task if it hasn't started yet.

Standard Executor implementation implement a thread interruption policy that allows tasks to be canceled through interruption. Hence, it is ok to call Future.cancel(true) which interrupts the thread.

You should not interrupt a pool thread directly when attempting to cancel a task because you won't know what task is running at the time the interrupt is delivered. Cancel only through the task's Future.

When Future.get() throws an InterruptedException or TimeoutException and you know that the result is no longer required, cancel the task by calling Future.cancel().

Dealing with non-interruptile blocking

synchronous socket I/O in java.io - read/write in InputStream and OutputStream are not responsive to interruption, but closing the underlying socket makes any threads blocked in read/write to throw a SocketException.

Synchronous I/IO in java.nio - Interrupting a thread waiting on an InterruptibleChannel causes it to throw ClosedByInterruptionException and close the channel. Closing an InterruptibleChannel cause threads blocked on channel operations to throw AsynchronousCloseException.

Asynchronous IO with Selector - A thread blocked in Selector.select() returns prematurely if close() or wakeup() is called.

To perform non standard cancellation tasks (like closing a socket), override newTaskFor() in ThreadPoolExecutor to return a CancellableTask. CancellableTask extends Callable and overrides the FutureTask.cancel() method to close socket or perform any other nonstandard cancellation tasks.

Stopping a thread-based service

A thread pool owns the worker threads, and should be responsible for stopping them. It should provide lifecycle methods that can be used by the application to shut down the pool, which in turn shuts down the worker threads.

ExecutorService provides shutdown() and shutdownNow(). shutdownNow() returns the list of tasks that had not started, so they can be logged or saved for future processing. The returned Runnable objects may not be the same as what was submitted - they may be wrapped.

shutdownNow() provides no way of knowing the state of tasks in progress at shutdown time, unless the tasks themselves do checkpointing. Another option is to override execute() of AbstractExecutorService and pass in a wrapper Runnable that records tasks cancelled at shutdown. There is a race condition that may cause a completed task to be marked as cancelled. So tasks must be idempotent.

Poison Pill - a special object placed on the work queue is another way to convince a producer-consumer service to shut down. Applicable only when the number of consumers and producers is known, and when the queue is unbounded.

Handling abnormal thread termination

Leading cause of premature thread death is RuntimeException. If nothing special is done, the exception bubbles all the way up the stack and the thread is killed after printing a stacktrace to the console (which no one may be watching for)

If you are writing a worker thread class for a thread pool or executor service, be sure to catch Throwable and then notify the executor service of premature thread death.

Thread API provides an UncaughtExceptionHandler facility - when a thread exits due to an uncaught exception, the JVM reports this to an application provided UncaughtExceptionHandler. Use Thread.setUncaughtExceptionHandler() to set the handler for the current thread or Thread.setDefaultUncaughtExceptionHandler to set it for all threads.

In long-running applications, always use uncaught exception handlers for all threads that at least log the exception.

To set an UncaughtExceptionHandler for pool threads, provide a ThreadFactory to the ThreadPoolExecutor. Exceptions thrown from tasks make it to the UncaughtExceptionHandler only for tasks submitted with execute(). For those submitted with submit(), the exceptions are rethrown when calling Future.get()

In orderly shutdown, JVM starts all shutdown hooks registered while Runtime.addShutdownHook(). Order of shutdown hook execution is not guaranteed.

After all shutdown hooks have completed, JVM may choose to run finalizers if runFinalizersOnExit is true,

JVM makes no attempt to stop or interrupt any application threads that are still running.

Shut-down hooks can run concurrently with other application threads. So, they must be thread-safe. Since JVM is shutting down, the application state may be messy. Hence the shut-down hooks must be coded extremely defensively.

Shut-down hooks should not use services that can be shutdown by the application or by other shutdown hooks. One option is to use a single shutdown hook per application that executes various shutdown operations in sequence.

Daemon threads - existence of a Daemon thread does not prevent JVM from shutting down. Internal JVM threads like GC thread are daemon threads. When JVM exits, finally blocks of any existing daemon threads are not run.

Should be used sparingly, for activities that can be safely abandoned at any time without any cleanup.

Do not use daemon threads for any tasks that perform I/O.

Generally used for housekeeping tasks like a background thread to remove expired cache entries.

Finalizers

GC treats objects that have a non-trivial finalize() method specially. finalize() is called after the memory is reclaimed.

Finalizers can run concurrently with application threads. Hence, they must be thread-safe.

No guarantee about if or when they will run.

HIgh performance cost.

Usually, finally blocks and explicit close statements are sufficient to release resources, instead of using finalize().

finalizers may be needed for objects that hold resources acquired by native methods.