If you’re reading this, you’re probably wondering why, and most importantly: how do I stop a thread if I can’t just rely on the Thread#stop() method, which has been deprecated… since Java 1.2?
The quick answer is that one should use interruptions because Thread#stop is unsafe.
But if you want to understand why, how, and what is this annoying InterruptedException for… keep reading.
The Problem with Thread#stop()
As much as it sounds intuitive to stop an already started thread, starting and stopping are two highly different cases. Why is that?
When we start a new thread, we’re starting from scratch, which is a clearly-defined state, but when we try to stop an existing thread, it can be in the middle of an operation that must not be terminated immediately.
Imagine a thread making changes to a thread-safe data structure. While being is in the middle of the critical section… and the thread is magically gone – locks get released, and now another thread can observe the data structure left in an inconsistent state.
Consider the following example of a thread-safe counter with some internal state:
class ThreadSafeCounter { private volatile int counter = 0; private volatile boolean dirty = false; public synchronized int incrementAndGet() { if (dirty) { throw new IllegalStateException("this should never happen"); } dirty = true; // ... Thread.sleep(5000); // boilerplate not included // ... counter = counter + 1; dirty = false; return counter; } }
As you can see, the getAndIncrement() method increments the counter and modifies the dirty flag. Locks guarantee that each time a thread enters getAndIncrement(), the dirty flag is set to false.
Now, let’s create a thread and stop it abruptly:
var threadSafeCounter = new ThreadSafeCounter(); var t1 = new Thread(() -> { while (true) { threadSafeCounter.incrementAndGet(); } }); t1.start(); Thread.sleep(500); t1.stop(); threadSafeCounter.incrementAndGet(); // Exception in thread "main" java.lang.IllegalStateException: this should never happen
Now threadSafeCounter instance, despite being correctly implemented, is permanently corrupted due to an abrupt stop of a thread.
How do we fix it?
Cooperative Thread Interruption
Instead of stopping a thread, we should rely on cooperative thread interruption. In simple words, we should ask a thread to stop itself in the right moment by using Thread#interrupt.
However, calling Thread#interrupt instead of Thread#stop is not enough. A safe thread termination can be achieved only as a result of the cooperation of a caller and the thread, which means that inside a thread, we need to handle interruption signals which come in two forms:
- A boolean interrupted flag
- InterruptedException
Handling The Interrupted Flag
While inside a thread, we can check if it was interrupted by calling one of the dedicated methods:
Thread.currentThread().isInterrupted(); // reads the interrupted flag Thread.interrupted(); // reads and resets the interrupted flag
There’s no universal way of handling an interruption, we need to apply an action that fits our context.
In the example from the previous section, a thread is incrementing our counter in an infinite loop. Instead of stopping immediately, we could simply wait until a thread finishes its incrementation job and then exit the loop:
var threadSafeCounter = new ThreadSafeCounter(); var t1 = new Thread(() -> { while (!Thread.interrupted()) { threadSafeCounter.incrementAndGet(); } }); t1.start(); Thread.sleep(500); t1.interrupt(); threadSafeCounter.incrementAndGet();
Now the thread exits safely in the desired moment without causing any havoc.
Handling the InterruptedException
However, what if we try to interrupt a thread that’s currently waiting? Naturally, we can’t poll the interruption flag because a thread is preoccupied with a blocking operation.
This is why the InterruptedException exists. It’s not a standard exception, but another form of an interruption signal which exists purely for dealing with interruptions of blocking operations! Similar to Thread#interrupted, it resets the interruption flag.
Let’s modify the above example again and introduce pauses before each incrementation. Now, we need to process an InterruptedException thrown by Thread#sleep:
var threadSafeCounter = new ThreadSafeCounter(); var t1 = new Thread(() -> { while (!Thread.interrupted()) { threadSafeCounter.incrementAndGet(); try { Thread.sleep(10000); } catch (InterruptedException e) { break; } } }); t1.start(); Thread.sleep(500); t1.interrupt(); assertThat(threadSafeCounter.incrementAndGet()).isGreaterThan(0);
In our case, it was enough to just exit the loop.
However, what if you don’t feel like a particular method should be responsible for interruption handling? what if the method signature can’t be changed?
In such case, we need to restore the interruption flag and/or repackage the exception, for example:
try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); }
Summary
- Thread#stop is not deprecated without a reason – it’s deeply unsafe
- Threads are stopped via interruptions – signals interpreted by a thread itself
- InterruptedException is not an ordinary exception – it’s a Java-native way of signaling thread interruption
- Thread.currentThread().isInterrupted() and Thread.interrupted() are not the same – the first one just reads the interrupted flag while the other resets it after
- there’s no universal way of handling interruption signals – “it depends”
Source code can be found on GitHub.