1. Overview

Thread interruption is a mechanism to signal a thread that it must stop its execution at a convenient point. However, it is up to the running task whether it checks the interruption status and stops. In this tutorial, we're going to examine how we can use the thread interruption mechanism in Java.

2. Interrupt using Thread.interrupt

Firstly, we'll see how we can interrupt a threadWhen we call interrupt on a Thread instance, it sets the thread's interruption status so that other parts of the code can inspect and act on it.

public void interruptThread() {
    final Runnable task = () -> {
        int i = 0;
        while (i < Integer.MAX_VALUE) {
            i++;
        }
    };
    final Thread thread = new Thread(task);
    thread.start();

    System.out.println("Is thread interrupted: " + thread.isInterrupted());
    thread.interrupt();
    System.out.println("Is thread interrupted: " + thread.isInterrupted());
}

In this example, we're first creating a Runnable task. Then we're starting a Thread using this task. Lastly, we're observing the interruption status before and after interrupting the thread.

A sample run prints:

Is thread interrupted: false
Is thread interrupted: true

As expected, the thread's interruption flag becomes true after the interrupt call.

Generally, there are two parties interested in an interrupt: the code executing the task and the code owning the thread. Moreover, they can act separately on an interruption request. The previous example contains both the task and the thread owner. Our code is the owner because it creates the thread. And it is the owner's responsibility to handle an interrupt. For example, it can terminate the thread or take any other action. Since we don't know the exact impact, only the owner must interrupt a thread.

For example, when working with a thread pool, the tasks don't own the worker thread, they just borrow them from the underlying pool implementation. So when we submit a task to a thread pool, the task can respond to an interrupt and possibly cancel its operation. But it must not interrupt the worker thread invoking Thread.interrupt. The ExecutorService implementations encapsulate the interruption mechanism and provide the shutdownNow method. This method enables us to interrupt the worker threads under the supervision of the thread pool.

3. Check Interruption Status

In the previous example, we used Thread.isInterrupted to check whether a thread is interrupted or not. When a caller performs this check, the interruption flag isn't cleared. In other words, when called multiple times, isInterrupted returns the same boolean value:

new Thread(() -> {
    Thread.currentThread().interrupt();
    System.out.println("Is thread interrupted: " + Thread.currentThread().isInterrupted());
    System.out.println("Is thread interrupted: " + Thread.currentThread().isInterrupted());
}).start();

Here, we first interrupt the current thread. Then the successive isInterrupted calls both return true:

Is thread interrupted: true
Is thread interrupted: true

The Thread class also provides a static variant: Thread.interrupted. It checks the interruption status of the current thread. Additionally, it clears the interruption flag:

new Thread(() -> {
    Thread.currentThread().interrupt();
    System.out.println("Is thread interrupted: " + Thread.interrupted());
    System.out.println("Is thread interrupted: " + Thread.interrupted());
}).start();

The output shows this behavior:

Is thread interrupted: true
Is thread interrupted: false

4. Respond to Interruption

Next, we'll look at how we can respond to a thread interrupt. As mentioned before, an interrupt can have multiple recipients: the task and the thread.

For the tasks, we must use it to cancel the running operation and exit immediately if possible. This is also the approach taken by most of the Java class libraries.

For the threads, if the code doesn't own the threads - when using a thread pool for example - or isn't responsible for applying the interruption policy, it must preserve the interrupt request. To achieve this, we must either throw an InterruptedException or set the status again.

4.1. Clear and Throw InterruptedException

As the first option, we'll consider throwing an InterruptedException upon an interrupt. If the task supports cancellation, we must stop its execution. After the cancellation, we throw an InterruptedException to let the code higher on the stack handle the interrupt.

Take a look at the example:

public void handleAndThrowException() throws InterruptedException {
    final ExecutorService executorService = Executors.newSingleThreadExecutor();

    final Callable<Void> task = () -> {
        while (true) {
            if (Thread.interrupted()) {
                System.out.println("Interrupted, cleaning up and then throwing exception.");
                throw new InterruptedException();
            }

            // Do work.
        }
    };

    final Future<?> future = executorService.submit(task);

    TimeUnit.SECONDS.sleep(1); // Wait for some time

    final boolean cancel = future.cancel(true);
    System.out.println("Is cancelled?: " + cancel);

    executorService.shutdown();
}

Here, we're using a thread pool to execute our task. In the Callable task, we have a while loop that performs some work. Note that our task relies on thread interruption for cancellation. When it detects an interrupt, it cleans up and throws an InterruptedException. This way we're behaving politely and preserving the interruption status so that the pool gets a chance to act on it. Notice that we aren't directly calling Thread.interrupt. Instead, we're requesting an interrupt calling Future.cancel.

4.2. Clear and Interrupt Again

In a Runnable task, we can't throw an InterruptedException. Or we may not want to throw a checked exception. In these cases, we must set the interruption status again if cleared.

while (true) {
    if (Thread.interrupted()) {
        System.out.println("Interrupted, cleaning up and then throwing exception.");
        Thread.currentThread().interrupt();
        return "Canceled";
    }
    // Do work.
}

Similar to the previous example, the task is responsive to interrupts and stops as soon as possible. Moreover, it maintains the interruption status for the caller code.

4.3. Respond to InterruptedException

If our code is the caller and catches an InterruptedException, we have the same options as the previous ones. We can either rethrow the exception or set the interruption status invoking the interrupt method:

try {
    TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
    System.out.println("Interrupted, cleaning up and then setting the interrupt status.");
    Thread.currentThread().interrupt();
    return "Canceled";
}

return "Ok";

Here, we're opting for the second approach and setting the status again.

4.4. Check Without Clearing

Alternatively, we can use isInterrupted to check the status. Since it doesn't clear the status, we don't need to throw an InterruptedException or set the status again.

while (true) {
    if (Thread.currentThread().isInterrupted()) {
        System.out.println("Interrupted, cleaning up and then throwing exception.");
        return "Canceled";
    }

    // Do work.
}

4.5 Swallow Interruption Request

Lastly, if the code is applying the interruption policy for the thread, it can clear the interruption status. This implies that the code isn't running on a thread pool and manages its own threads.

5. Summary

In this tutorial, we've looked at how we can use thread interruption in Java. We firstly investigated the interruption methods in the Thread class. Then we detailed different approaches to handle an interruption request.

Lastly check out the source code for all examples in this tutorial over on Github.