Thread.interrupt()
The only sensible way to cancel an executing Java thread is by
interrupting it. This will set a boolean interrupted status flag
in the thread object to true. Calling interrupt on a thread does not
mean that it will immediately stop. It just requests the thread to
interrupt itself at the next blocking call. These blocking calls
comprise methods such as sleep, wait and join. They are known as
cancellation points. If the interrupted status is set, these methods
clear that flag and throw an InterruptedException. What should a
caller do if it catches an InterruptedException? The answer depends
on the ownership of the thread. The object that launched the thread is
considered its owner. For instance, a thread pool is the owner of all
worker threads that it started. Only the owner knows what to do when a
thread is interrupted. Code executed by the owner when a thread is
interrupted is known as its interruption policy or cancellation
policy. The owner will be denied the chance to execute this policy if
the InterruptedException is not propagated up to it. To make this
idea clear, let us look at the implementation of a simple
TaskExecutor. This class receives a Task and executes it in a
thread. TaskExecutor also exposes a method to cancel a running
task. A task is represented by this simple interface:
public interface Task { public void execute () throws InterruptedException; }
execute throws the InterruptedException so that implementers get a
chance to propagate it to the TaskExecutor.
The TaskExecutor receives a task through its submit method. This
task is passed on to a TaskThread where it is executed. The
TaskExecutor object owns this thread. When it is interrupted, the
TaskThread calls the appropriate method that executes the
interruption policy as defined by TaskExecutor. An interruption
policy may free up some thread specific resource, reset some state or
do something else that only the owner knows of. (As we have only one
thread in the TaskExecutor, the thread also calls a global cleanup
method before it finish running. In the case of a thread pool, this
method might be called before the last thread is interrupted or when
it exists normally). Here is the code that defines the TaskThread
class:
public class TaskThread extends Thread { public TaskThread (Task task, TaskExecutor owner) { this.task = task; this.owner = owner; } public void run () { try { task.execute (); } catch (InterruptedException ex) { if (owner != null) owner.executeInterruptionPolicy (); } finally { if (owner != null) { owner.cleanup (); } } } private Task task; private TaskExecutor owner; }
Now, the definition of TaskExecutor itself:
public class TaskExecutor { public void submit (Task task) { t = new TaskThread (task, this); t.start (); running = true; } public void executeInterruptionPolicy () { System.out.println ("executing interruption policy ..."); } public void cleanup () { if (running) { System.out.println ("cleaning up ..."); running = false; } } public void cancel () { if (running) t.interrupt (); } public boolean isRunning () { return running; } private TaskThread t; private boolean running; }
The cancel method just calls interrupt on the running thread, so
that, if the task makes a blocking call on the thread, it will throw
an InterruptedException. As the Task implementation do not own the
thread and cannot be sure of its interruption policy, it should not
consume this exception. Here is a Task implementation that correctly
hands over the InterruptedException to someone who knows what it
means to interrupt this particular thread:
public class CounterTask implements Task { public void execute () throws InterruptedException { int i = 0; while (i < 10) { System.out.print (++i + " "); try { Thread.sleep (500); } catch (InterruptedException ex) { throw ex; } } } }
The following code submits a CounterTask to the executor. User can
cancel the task by typing the ‘q’ key:
public class Test { public static void main (String args[]) { TaskExecutor t = new TaskExecutor (); t.submit (new CounterTask ()); while (t.isRunning ()) { try { int c = System.in.read (); if ((char)c == 'q') { t.cancel (); break; } } catch (java.io.IOException ex) { } } }
A sample run:
$ java.exe Test 1 2 3 4 5 6 q executing interruption policy ... cleaning up ...
If you comment out throw ex; in CounterTask.execute(),
TaskExecutor.executeInterruptionPolicy() will not run.
To summarize :– The proper way to cancel a running thread is to call
interrupt on it. Its interrupted status can either be polled (by
calling isInterrupted) or by catching the InterruptedException.
Consume this exception only if you own the thread. Otherwise re-throw
the exception and give the owner of the thread a chance to execute the
proper interruption policy.