In the previous post on stopping threads, we explored thread design strategies to safely stop threads in Java. In this post, let’s look at various ways we can stop or cancel tasks handled by Executors (and ExecutorServices).
When we submit a task (Callable or its older cousin Runnable) for execution to an Executor or ExecutorService (e.g. ThreadPoolExecutor), we get a Future back which wraps the tasks. It has a method called cancel(…) which can be called to cancel the taskFuture wraps. Calling this method has different effects depending on the stage the task is in. A task could be in three possible stages after being submitted to an Executor:
- The task hasn’t started executing yet – it is waiting in the work queue for a thread to start executing it.
- A thread is executing the task.
- The task has finished executing.
Cancellation is trivial, if the task hasn’t started executed. It is simply removed from the work queue. Similarly, if the task has finished executing, cancelling it has no effect.
It is a little tricky when the task is executing in a thread. Recall from my previous post: to stop threads in Java, we rely on a co-operative mechanism called Interruption. The idea is very simple. To stop a thread, all we can do is deliver it a signal, aka interrupt it, requesting that the thread stops itself at the next available opportunity. If the thread cooperates, it will clean up itself and stop. Non-cooperating threads ignore the request and cancellation will have no effect.
boolean cancel(boolean mayInterruptIfRunning)Attempts to cancel execution of this task. This attempt will fail if the task has already completed, has already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
So when the tasks is already executing and we call cancel(true) on it, it will deliver an interrupt signal to the thread executing the task. In order to make this work properly, your threads must be designed to handle interruption. Refer to this post for more info.
Sometime, it becomes necessary to support non-standard task cancellation – especially when the task relies on blocking or long-running methods that are oblivious to interruption. E.g. when you call ServerSocket.accept(), it starts waiting for a client connection. The catch-22 is that it will ignore all interruption requests and if this function is called in a thread, you cannot stop that thread using interrupts. To support nonstandard cancellation where interrupts won’t work, there are two ways of doing it. Please remember, in both of the following ways, you will have to do something to cancel the method that ignores interrupts. E.g. in the case of ServerSocket, closes the underlying socket which forces the accept() method to throw an exception.
1. Overriding Thread.interrupt():
Provide a custom ThreadFactory to the ExecutorService. Return custom Threads which override the interrupt method. For example:
Overriding interrupt() method is not recommended.
2. Overriding Future.cancel(…):
In your tasks (e.g. Callable) provide a method for non-standard cancellation, such as cancelTask(). Then override the Future.cancel(…) to call cancelTask().
But think about it: We do not normally create a Future ourselves and specify what the cancel(…) method does: we get Future when we submit a task to an ExecutorService via ExecutorService.submit(…).
Luckily, ExecutorService calls on a method called newTaskFor(Callable c) that returns the Future (or rather RunnableFuture) representing the task. Hence we need to override newTaskFor(…) to return a custom Future which overrides the cancel(…) method. This is shown below with an example:
Whereas the IdentifiableCallable is shown below:
Next we need to define our own FutureWrapper so we can override the cancel(…) method:
Now we need to define our task as follows:
That’s all. Now when we call FutureTaskWrapper.cancel(…), it will in turn call cancelTask(), where we can do our non-standard cancellation.
The entire code used in this post is available on GitHub.