Preface
For InterruptedException, a common way of handling it is to "swallow" it - catch it and do nothing (or record it, but that's not much better) - just like Listing 4 later. Unfortunately, this approach ignores the fact that interrupts may occur during this period, and interrupts may cause the application to lose the ability to cancel activity or close it in time.
Blocking method
When a method throws an InterruptedException, it not only tells you that it can throw a specific check exception, but it also tells you something else. For example, it tells you that it is a blocking method, which will try to eliminate the blocking and return as early as possible if you respond properly.
The blocking method is different from the general method that takes a long time to run. The completion of a general method depends only on what it is doing and whether there is enough computing resources available (CPU cycles and memory). The completion of the blocking method also depends on some external events, such as timer expiration, I/O completion, or another thread's actions (release a lock, set a flag, or place a task in a work queue). General methods end after their work is done, while blocking methods are more difficult to predict because they depend on external events. Blocking methods can affect responsiveness because it is difficult to predict when they will end.
The blocking method may not be terminated because it cannot wait for the event it is waiting for, so it is very useful to make the blocking method cancelable (and it is often very useful if long-running non-blocking methods are cancelable). A cancelable operation refers to an operation that can be terminated from the outside before normal completion. The interrupt mechanism provided by Thread and supported by Thread.sleep() and Object.wait() is a cancel mechanism; it allows one thread to request another thread to stop what it is doing. When a method throws an InterruptedException, it is telling you that if the thread executing the method is interrupted, it will try to stop what it is doing and return in advance, and by throwing an InterruptedException, it returns in advance. A well-behaved blocking library method should respond to interrupts and throw an InterruptedException so that it can be used in cancelable activities without affecting the response.
Thread interrupt
Each thread has a Boolean attribute associated with it, which represents the interrupted status of the thread. The interrupt state is false at the beginning; when another thread interrupts a thread by calling Thread.interrupt(), one of two situations occurs. If that thread is executing a low-level interruptible blocking method, such as Thread.sleep(), Thread.join(), or Object.wait(), it will unblock and throw an InterruptedException. Otherwise, interrupt() just sets the interrupt state of the thread. After the code running in the interrupted thread can poll the interrupt state to see if it is requested to stop what it is doing. The interrupt state can be read by Thread.isInterrupted() and can be read and cleared by an operation named Thread.interrupted().
Interruption is a collaborative mechanism. When one thread interrupts another thread, the interrupted thread does not necessarily stop what it is doing immediately. Instead, an interrupt is a polite request to another thread to stop what it is doing when it is willing and convenient. Some methods, such as Thread.sleep(), take such requests very seriously, but each method does not necessarily respond to interrupts. For interrupt requests, methods that do not block but still take a long time to execute can poll the interrupt status and return in advance when interrupted. You can ignore interrupt requests at will, but doing so will affect the response.
One benefit of the collaborative nature of interruptions is that it provides greater flexibility to safely construct cancelable activities. We rarely want an activity to stop immediately; if the activity is cancelled while an update is in progress, the program data structure may be inconsistent. The interrupt allows a cancelable activity to clean up ongoing work, restore invariants, notify other activities that it is to be canceled before it is terminated.
Handle InterruptedException
If throwing an InterruptedException means that a method is a blocking method, calling a blocking method means that your method is also a blocking method, and you should have some kind of strategy to handle InterruptedException. The easiest strategy is usually to throw an InterruptedException yourself, as shown in the code in the putTask() and getTask() methods in Listing 1. Doing so makes the method respond to interrupts and simply add an InterruptedException to the throws clause.
Listing 1. Not catching InterruptedException, propagating it to the caller
public class TaskQueue { private static final int MAX_TASKS = 1000; private BlockingQueue<Task> queue = new LinkedBlockingQueue<Task>(MAX_TASKS); public void putTask(Task r) throws InterruptedException { queue.put(r); } public Task getTask() throws InterruptedException { return queue.take(); }} Sometimes some cleaning work is required before the exception is propagated. In this case, you can catch the InterruptedException, perform a cleanup, and then throw an exception. Listing 2 demonstrates this technique, a mechanism used to match players in online gaming services. The matchPlayers() method waits for two players to arrive and then starts a new game. If the method is interrupted when one player has arrived but another player has not arrived, it will put that player back in the queue and re-throw InterruptedException so that the player's request to the game will not be lost.
Listing 2. Perform task-specific cleanup work before re-throwing InterruptedException
public class PlayerMatcher { private PlayerSource players; public PlayerMatcher(PlayerSource players) { this.players = players; } public void matchPlayers() throws InterruptedException { try { Player playerOne, playerTwo; while (true) { playerOne = playerTwo = null; // Wait for two players to arrive and start a new game playerOne = players.waitForPlayer(); // could throw IE playerTwo = players.waitForPlayer(); // could throw IE startNewGame(playerOne, playerTwo); } } catch (InterruptedException e) { // If we got one player and were interrupted, put that player back if (playerOne != null) players.addFirst(playerOne); // Then propagate the exception throw e; } }} Don't stop swallowing alive
Sometimes throwing an InterruptedException is not appropriate, for example, when a task defined by a Runnable calls an interruptible method. In this case, the InterruptedException cannot be re-throwed, but you don't want to do nothing either. When a blocking method detects an interrupt and throws an InterruptedException, it clears the interrupt state. If an InterruptedException is caught but cannot be re-throwed, then evidence of the interrupt occur should be kept so that higher-level code in the call stack can know the interrupt and respond to it. This task can be done by calling interrupt() to "reinterrupt" the current thread, as shown in Listing 3. At least, whenever an InterruptedException is caught and it is not re-thrown, the current thread is re-interrupted before returning.
Listing 3. Resuming interrupted state after capturing InterruptedException
public class TaskRunner implements Runnable { private BlockingQueue<Task> queue; public TaskRunner(BlockingQueue<Task> queue) { this.queue = queue; } public void run() { try { while (true) { Task task = queue.take(10, TimeUnit.SECONDS); task.execute(); } } catch (InterruptedException e) { // Restore the interrupted status Thread.currentThread().interrupt(); } }} The worst thing to do when dealing with InterruptedException is to swallow it raw - catch it, and then neither re-throw it nor re-assert the interrupt state of the thread. For an exception that you don't know how to deal with, the most standard way to handle is to catch it and record it, but this method is still like a raw interrupt, because higher-level code in the call stack still cannot obtain information about the exception. (It is not wise to just record InterruptedException, because it is too late to process it when someone comes to read the log.) Listing 4 shows a widely used pattern, which is also a pattern of raw swallowing interruption:
List 4. Raw swallowing interruption - don't do this
// Don't do this public class TaskRunner implements Runnable { private BlockingQueue<Task> queue; public TaskRunner(BlockingQueue<Task> queue) { this.queue = queue; } public void run() { try { while (true) { Task task = queue.take(10, TimeUnit.SECONDS); task.execute(); } } catch (InterruptedException swallowed) { /* DON'T DO THIS - RESTORE THE INTERRUPTED STATUS INSTEAD */ } }} If the InterruptedException cannot be re-throwed, regardless of whether you plan to process the interrupt request or not, you still need to re-interrupt the current thread, as one interrupt request may have multiple "receivers". The standard thread pool (ThreadPoolExecutor) worker thread implementation is responsible for interruption, so interrupting a task in a running thread pool can have a dual effect. One is to cancel the task, and the other is to notify the execution thread that the thread pool is about to be closed. If the task devours the request, the worker thread will not know that there is a requested interrupt, delaying the shutdown of the application or service.
Implement cancelled tasks
There is no specific semantics for interrupts in the language specification, but in larger programs, it is difficult to maintain any interrupt semantics except cancellation. Depending on what activity is, users can request cancellation through a GUI or through a network mechanism, such as JMX or Web Service. The program logic can also be requested to cancel. For example, a web crawler will automatically shut itself down if it detects that the disk is full, otherwise a parallel algorithm will start multiple threads to search for different areas of the solution space, and cancel those threads once one of the threads finds a solution.
Just because a task is cancelable does not mean that an interrupt request needs to be responded immediately. For tasks that execute code in a loop, it is usually only necessary to check for interrupts once for each loop iteration. Depending on how long the loop is executed, it may take some time for any code to notice that the thread has been interrupted (either poll the interrupt state by calling the Thread.isInterrupted() method, or call a blocking method). If the task needs to be responsive, it can poll the interrupt state more frequently. The blocking method usually polls the interrupt state immediately at the inlet and, if it is set to improve responsiveness, it also throws an InterruptedException.
The only time you can stop swallowing alive is that you know the thread is about to exit. This scenario only occurs when the class that calls the interruptible method is part of Thread, not Runnable or general library code, as shown in Listing 5. Listing 5 Creates a thread that lists prime numbers until it is interrupted, which is also allowed to exit when it is interrupted. The loop used to search for primes checks for interruptions in two places: one is to poll the isInterrupted() method at the head of the while loop, and the other is to call the blocking method BlockingQueue.put().
Listing 5. If you know that the thread is about to exit, you can swallow the interrupt
public class PrimeProducer extends Thread { private final BlockingQueue<BigInteger> queue; PrimeProducer(BlockingQueue<BigInteger> queue) { this.queue = queue; } public void run() { try { BigInteger p = BigInteger.ONE; while (!Thread.currentThread().isInterrupted()) queue.put(p = p.nextProbablePrime()); } catch (InterruptedException consumed) { /* Allow thread to exit */ } } public void cancel() { interrupt(); }} Uninterruptible blocking method
Not all blocking methods throw InterruptedException. The input and output stream classes block waiting for I/O to complete, but they do not throw InterruptedException and will not return in advance if interrupted. However, for socket I/O, if a thread closes the socket, the blocking I/O operation on that socket will end early and a SocketException is thrown. The non-blocking I/O classes in java.nio do not support interruptible I/O, but they can also be unblocked by closing the channel or requesting wake-up on the Selector. Similarly, attempting to acquire an internal lock (entering a synchronized block) cannot be interrupted, but ReentrantLock supports interruptible acquisition mode.
Uncancelable tasks
Some tasks refuse to be interrupted, which makes them unabolized. However, even non-cancellable tasks should try to preserve the interrupt state in case higher-level code on the call stack needs to process interrupts after the non-cancellable tasks are over. Listing 6 shows a method that waits for a blocking queue until an available item appears in the queue, regardless of whether it is interrupted or not. For convenience, it resumes the interrupt state after the end in a finally block, so as not to deprive the caller of the interrupt request. (It cannot resume interrupt state earlier, because that will cause an infinite loop - BlockingQueue.take() will immediately poll the interrupt state at the entrance, and, if the interrupt state set is found, an InterruptedException will be thrown.)
Listing 6. Uncancelable tasks that restore interrupted state before returning
public Task getNextTask(BlockingQueue<Task> queue) { boolean interrupted = false; try { while (true) { try { return queue.take(); } catch (InterruptedException e) { interrupted = true; // fall through and retry } } } finally { if (interrupted) Thread.currentThread().interrupt(); }} Summarize
You can use the collaboration interrupt mechanism provided by the Java platform to construct flexible cancellation strategies. Activities can decide at their own discretion whether they are cancelable or non-cancellable, and how to respond to interrupts, and can also postpone interrupts if immediate return would compromise application integrity. Even if you want to ignore interrupts completely in your code, you should make sure that the interrupt state is restored without re-throwing it, so that the code calling it does not know what the interrupt is happening. The above is the full content of Java's theory and practice of handling InterruptedException exceptions. I hope this article will be helpful to you. If you have any questions, please leave a message for discussion.