213. Interruptible methods

By an interruptible method, we mean a blocking method that may throw InterruptedException, for example, Thread.sleep(), BlockingQueue.take(), BlockingQueue.poll(long timeout, TimeUnit unit), and so on. A blocking thread is usually in a BLOCKED, WAITING, or TIMED_WAITING state, and, if it is interrupted, then the method tries to throw InterruptedException as soon as possible.

Since InterruptedException is a checked exception, we must catch it and/or throw it. In other words, if our method calls a method that throws InterruptedException, then we must be prepared to deal with this exception. If we can throw it (propagate the exception to the caller), then it is not our job anymore. The caller has to deal with it further. So, let's focus on the case when we must catch it. Such a case can occur when our code is run inside  Runnable, which cannot throw an exception.

Let's start with a simple example. Trying to get an element from  BlockingQueue via poll(long timeout, TimeUnit unit) can be written as follows:

try {
queue.poll(3000, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
...
logger.info(() -> "Thread is interrupted? "
+ Thread.currentThread().isInterrupted());
}

Attempting to poll an element from the queue can result in InterruptedException. There is a window of 3,000 milliseconds in which the thread can be interrupted. In case of an interruption (for example, Thread.interrupt()), we may be tempted to think that calling Thread.currentThread().isInterrupted() in the catch block will return true. After all, we are in an InterruptedException catch block, so it makes sense to believe this. Actually, it will return false, and the answer is in the source code of the poll(long timeout, TimeUnit unit) method listed as follows:

1: public E poll(long timeout, TimeUnit unit) 
throws InterruptedException {
2: E e = xfer(null, false, TIMED, unit.toNanos(timeout));
3: if (e != null || !Thread.interrupted())
4: return e;
5: throw new InterruptedException();
6: }

More precisely, the answer is in line 3. If the thread was interrupted then Thread.interrupted() will return true and will lead to line 5 (throw new InterruptedException()). But beside testing, if the current thread was interrupted, Thread.interrupted() clears the interrupted status of the thread. Check out the following succession of calls for an interrupted thread:

Thread.currentThread().isInterrupted(); // true
Thread.interrupted() // true
Thread.currentThread().isInterrupted(); // false
Thread.interrupted() // false

Notice that Thread.currentThread().isInterrupted() tests whether this thread has been interrupted without affecting the interrupted status.

Now, let's get back to our case. So, we know that the thread was interrupted since we caught InterruptedException, but the interrupted status was cleared by Thread.interrupted(). This means also that the caller of our code will not be aware of the interruption.

It is our responsibility to be good citizens and restore the interrupt by calling the interrupt() method. This way, the caller of our code can see that an interrupt was issued and act accordingly. The correct code could be as follows:

try {
queue.poll(3000, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
...
Thread.currentThread().interrupt(); // restore interrupt
}
As a rule of thumb, after catching InterruptedException, do not forget to restore the interrupt by calling Thread.currentThread().interrupt().

Let's tackle a problem that highlights the case of forgetting to restore the interrupt. Let's assume a  Runnable that runs as long as the current thread is not interrupted (for example, while (!Thread.currentThread().isInterrupted()) { ... }).

At each iteration, if the current thread interrupted status is false, then we try to get an element from  BlockingQueue.

The following code is the implementation:

Thread thread = new Thread(() -> {

// some dummy queue
TransferQueue<String> queue = new LinkedTransferQueue<>();

while (!Thread.currentThread().isInterrupted()) {
try {
logger.info(() -> "For 3 seconds the thread "
+ Thread.currentThread().getName()
+ " will try to poll an element from queue ...");

queue.poll(3000, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
logger.severe(() -> "InterruptedException! The thread "
+ Thread.currentThread().getName() + " was interrupted!");
Thread.currentThread().interrupt();
}
}

logger.info(() -> "The execution was stopped!");
});

As a caller (another thread), we start the above thread, sleep for 1.5 seconds, just to give time to this thread to enter in the poll() method, and we interrupt it. This is shown in the following code:

thread.start();
Thread.sleep(1500);
thread.interrupt();

This will lead to InterruptedException.

The exception is logged and the interrupt is restored.

At the next step, while evaluates Thread.currentThread().isInterrupted() to false and exits.

As a result, the output will be as follows:

[18:02:43] [INFO] For 3 seconds the thread Thread-0
will try to poll an element from queue ...

[18:02:44] [SEVERE] InterruptedException!
The thread Thread-0 was interrupted!

[18:02:45] [INFO] The execution was stopped!

Now, let's comment on the line that restores the interrupt:

...
} catch (InterruptedException ex) {
logger.severe(() -> "InterruptedException! The thread "
+ Thread.currentThread().getName() + " was interrupted!");

// notice that the below line is commented
// Thread.currentThread().interrupt();
}
...

This time, the while block will run forever since its guarding condition is always evaluated to true.

The code cannot act on the interruption, so the output will be as follows:

[18:05:47] [INFO] For 3 seconds the thread Thread-0
will try to poll an element from queue ...

[18:05:48] [SEVERE] InterruptedException!
The thread Thread-0 was interrupted!

[18:05:48] [INFO] For 3 seconds the thread Thread-0
will try to poll an element from queue ...
...
As a rule of thumb, the only acceptable case when we can swallow an interrupt (not restore the interrupt) is when we can control the entire call stack (for example, extend Thread).

Otherwise, catching InterruptedException should contain Thread.currentThread().interrupt() as well.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset