Skip to content

Instantly share code, notes, and snippets.

@ceharris
Last active December 16, 2015 20:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ceharris/5494485 to your computer and use it in GitHub Desktop.
Save ceharris/5494485 to your computer and use it in GitHub Desktop.
An example of the (non-)effect of Thread.interrupt on a fairly typical long-running task.

I think that Thread.interrupt() sounds more intrusive than it really is. Thread interruption in Java is entirely advisory. Generally a task does not need to go to any effort to avoid being interrupted. Rather, it usually takes some real effort to properly observe the interrupt status in order to exit when interrupted, if such is desired. Short of putting in the effort to observe the thread's interrupt status, I think you'll find that arbitrary tasks are quite resilient in the face of interrupts.

A task can check the thread's interrupt status at any time by calling Thread.isInterrupted(). If you wish to make a runnable task interruptible, your code must occasionally check the interrupt status and exit the run() method if the task has been interrupted. If you don't want your task to be interrupted, simply ignore the thread's interrupt status.

JDK methods that examine the thread's interrupt status are consistently declared to throw InterruptedException, so they are easily identified. I/O calls won't respond to interrupt, because Thread interruption is an advisory concept of the JVM, and I/O is generally implemented using operating system calls.

If your code calls a method that throws InterruptedException you can catch the exception and decide whether your task should exit. One tricky issue is that programmers sometimes have a try/catch block as the top-level structure of the run() method, with a catch for Exception. If such a task happens to call an interruptible method the run method will exit after catching an InterruptedException. I think this serves as a really good example of why "blanket" catch blocks for Exception are a bit of a hazard, and should probably be avoided.

There are some risks when using making calls to third-party code that interrupt will not be handled appropriately, largely because thread interrupt is widely misunderstood. However, the most common case is that the mishandling results in the thread not exiting on interrupt, rather than exiting prematurely. It's possible that third-party code might look at the calling thread's interrupt status and decide to return or throw something other than InterruptedException, but I've found that to be the rare exception.

This gist provides a simple example of just how resilient a task can be to thread interruption. The class is a Runnable that compresses an input stream to an output stream in the run() method. The main method creates a compression job that will take a very long time to finish, and runs it on a separate thread. It then repeatedly attempts to interrupt the task, to no avail.

Note that we could easily make the run() method interruptible, by simply adding && !Thread.currentThread().isInterrupted() to the loop condition. If we did this, we might also want to check to see if the thread was interrupted after the loop and warn the user that the compression operation was interrupted.

import java.io.Closeable;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
public class Uninterruptible implements Runnable {
public static final int BUFFER_SIZE = 8192;
private final InputStream inputStream;
private final OutputStream outputStream;
public Uninterruptible(InputStream inputStream, OutputStream outputStream)
throws IOException {
this.inputStream = inputStream;
this.outputStream = new GZIPOutputStream(outputStream);
}
public void run() {
try {
byte[] buf = new byte[BUFFER_SIZE];
int numRead = inputStream.read(buf);
while (numRead != -1) {
outputStream.write(buf, 0, numRead);
}
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
finally {
closeQuietly(inputStream);
closeQuietly(outputStream);
}
}
private void closeQuietly(Closeable closeable) {
if (closeable == null) return;
try {
closeable.close();
}
catch (IOException ex) {
assert true; // avoid an empty catch block
}
}
public static void main(String[] args) throws Exception {
InputStream inputStream = new FileInputStream("/dev/urandom");
OutputStream outputStream = new FileOutputStream("/dev/null");
Runnable task = new Uninterruptible(inputStream, outputStream);
Thread thread = new Thread(task);
thread.start();
while (thread.isAlive()) {
Thread.sleep(1000);
thread.interrupt();
System.out.println("thread interrupted");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment