Java Institute DreamsCity 2000
Welcome to DreamsCity
Return to Java Institute

HANDLING THOSE PESKY
INTERRUPTEDEXCEPTIONS

If you have done any thread-related programming in the Java
programming language, you have have been forced to deal with 
InterruptedExceptions. These exceptions appear in the throws clause 
of Thread.sleep(), Thread.join(), and Object.wait(). An 
InterruptedException allows code on another thread to interrupt 
your thread if, for example, your thread takes too long to process. 
Many programmers rarely use interruption, and find these exceptions 
annoying. But even if your code never interrupts other threads, 
there are two reasons you should care about interruption.  
 
o  InterruptedException is a checked exception, which means that
   your code must catch or propagate the exception, even if you
   never expect it to happen.
     
o  In the Java environment, you cannot typically rely on 
   controlling the entire process in which your code runs. This 
   is good, because it allows for the use of things like mobile 
   agents, container architectures, applets, and RMI. However, it 
   also means that even if you never call Thread.interrupt() on 
   one of your threads, somebody else probably will.
This tip compares three different strategies for handling InterruptedExceptions: propagate them, ignore them, or defer them. The first strategy is to propagate the exception back to whoever calls your code. Here's an example: file://throughout this example error checking omitted for brevity interface Task { public void run() throws InterruptedException; } class PropagatingTask implements Task { public void run() throws InterruptedException { Thread.sleep(1000); System.out.println("PropagatingTask completed"); } } public class TaskClient implements Runnable { public static final int taskCount = 1000; Task[] tasks; public void doMain(Task[] tasks) { this.tasks = tasks; Thread worker = new Thread(this); worker.start(); try { System.in.read(); } catch (java.io.IOException ioe) { System.out.println("I/O exception on input"); } System.out.println("==========================Shutting down"); worker.interrupt(); try { worker.join(); } catch (InterruptedException ie) { System.out.println("Unexpected interruption on main thread"); } } public void run() { try { for (int n=0; n<taskCount; n++) { tasks[n].run(); if (Thread.interrupted()) { System.out.println("Interrupted state detected by client"); throw new InterruptedException(); } } } catch (InterruptedException ie) { System.out.println("Interruption caught by client"); } } public static void main(String[] args) { try { Class cls = Class.forName(args[0]); Task[] tasks = new Task[taskCount]; for (int n=0; n<taskCount; n++) { tasks[n] = (Task) cls.newInstance(); } new TaskClient().doMain(tasks); } catch (Exception e) { e.printStackTrace(); } } } Try running TaskClient by entering the following on the command line: java TaskClient PropagatingTask TaskClient expects a single command line argument which names an implementation of the Task interface, in this case, Propagatingtask. TaskClient then creates 1000 tasks, and runs them on a background thread. Each PropagatingTask sleeps for one second and prints "PropagatingTask completed." You will probably get bored and want to interrupt the thread before all 1000 tasks complete. The main thread allows this by reading from System.in. Try this by pressing the Enter key. When you do this, the main thread calls interrupt() on the worker thread. It then calls join(); this allows the worker thread to complete before the main thread exits. Your output should end like this: PropagatingTask completed ==========================Shutting down Interruption caught by client Notice that no tasks complete after the interruption. This means that all the tasks not yet started never get the chance to start. It also means that the one task in progress is rudely interrupted in the middle of processing. Both of these behaviors are a consequence of the PropagatingTask allowing the InterruptedException to propagate all the way back to the caller. The PropagatingTask implementation is the simplest way to deal with InterruptedExceptions, and it has the advantage of allowing you to interrupt the thread so that no new tasks begin. However, this approach has two disadvantages: (1) the caller is forced to handle InterruptedExceptions, and (2) the task that was in progress is forcibly stopped; this might be unacceptable if the task left data in some invalid state. Here is another approach, one that addresses the first problem: class IgnoringTask implements Task { public void run() { long now = System.currentTimeMillis(); long end = now + 1000; while (now < end) { try { Thread.sleep(end-now); } catch (InterruptedException ie) { System.out.println("IgnoringTask ignoring interruption"); } now = System.currentTimeMillis(); } System.out.println("IgnoringTask completed"); } } IgnoringTask uses System.currentTimeMillis() to keep track of elapsed time, and if an InterruptedException is thrown, it catches the exception and goes back to finish its work. Because the InterruptedException is not thrown from the run() method, it is not declared as a checked exception, and clients do not have to handle it. Try running IgnoringTask by entering the following on the command line: java TaskClient IgnoringTask If you press Enter to interrupt the thread, you will see this output: ==========================Shutting down IgnoringTask ignoring interruption IgnoringTask completed IgnoringTask completed etc. Notice that "IgnoringTask completed" continues to be printed. As you can see, an IgnoringTask cannot be interrupted midstream. This is appropriate in most situations. Unfortunately, an IgnoringTask also prevents the thread from being interrupted at all. Even after you try to interrupt the thread, new tasks will continue to run. You have made your thread permanently uninterruptible, and other programmers who use your code are not likely to be happy. What you need is some way to guarantee that tasks already in progress will finish, but still provide some way to interrupt the thread. The DeferringTask class provides a solution: class DeferringTask implements Task { public void run() { long now = System.currentTimeMillis(); long end = now + 1000; boolean wasInterrupted = false; while (now < end) { try { Thread.sleep(end-now); } catch (InterruptedException ie) { System.out.println("DeferringTask deferring interruption"); wasInterrupted = true; } now = System.currentTimeMillis(); } System.out.println("DeferringTask completed"); if (wasInterrupted) { Thread.currentThread().interrupt(); } } } DeferringTask is almost exactly the same as IgnoringTask, with one crucial difference. DeferringTask remembers that it was interrupted by setting the boolean flag wasInterrupted. When the task completes, DeferringTask calls interrupt() to reset the interrupt flag. Because interrupt() sets a flag instead of throwing an InterruptedException, the client does not have to catch an InterruptedException. Instead, it can check the setting of the interrupt flag by calling Thread.interrupted(), which is what TaskClient.run() does. Try running DeferringTask as follows: java TaskClient DeferringTask When you trigger the interrupt() by pressing enter, you should see output that ends like this: ==========================Shutting down DeferringTask deferring interruption DeferringTask completed Interrupted state detected by client Interruption caught by client Notice that a single DeferringTask completes after interruption. This was the one task in progress. However no new tasks begin because DeferringTask resets the interrupt flag, and that stops the thread. No single interruption strategy is appropriate for all situations. Here is a summary of the three strategies in this tip: ---------------------------------------------------------------- | Strategy | Client must | Tasks forcibly | No new tasks begin | | | catch IE? | stopped | after interrupt? | |----------------------------------------------------------------| | propagate| yes | yes | yes | | ignore | no | no | no | | defer | no* | no | yes* | |----------------------------------------------------------------| | * for defer to work correctly, caller must check for | | interruption | ---------------------------------------------------------------- For a more in-depth look at interruption, refer to Chapter 9 of Multithreaded Programming with Java Technology, by Bil Lewis and Daniel J. Berg (for information about this book, see http://www.sun.com/books/catalog/lewis3/).

Any comments? email to:
richard@dreamscity.net