First, let's take a look at the usage conditions.
If we have a thread pool and use interruption as the cancellation mechanism, we can only interrupt the worker threads through the pool. In other words, we can't directly invoke Thread.interrupt
since we don't own the threads. So, we must acquire a Future
and invoke Future.cancel
. Or we must call ExecutorService.shutdownNow
to cancel all tasks interrupting the busy threads. In the first case, it requires some bookkeeping on our side to hold the Future
handles. So the application must keep new tasks and remove the old ones.
On the other hand, if you use a global cancellation flag, we can cancel/stop multiple tasks from a central place without additional bookkeeping. But if we want to cancel an individual task - similar to invoking Future.cancel
- we must store a cancellation flag for each task.
Secondly, let's examine the general convention.
Java class libraries generally interpret a thread interrupt as a cancellation request. For example, LinkedBlockingQueue.take
can make our program block. But when the current thread is interrupted it throws an InterruptedException
and returns. So our application becomes responsive by using a responsive method. So we can just build upon the already existing support and write additional Thread.interrupted/Thread.currentThread().isInterrupted
checks in our code.
Moreover, the cancellation methods in ExecutorService
use thread interruption. As we mentioned, Future.cancel
and ExecutorService.shutdownNow
rely on interrupts.