sleep, join & interrupt in Java
Cooperative cancellation, InterruptedException, restoring the interrupt flag.
Interrupt is a polite note
t.interrupt() only sets a flag on t. It never forcibly stops it. A thread busy calculating keeps going until it checks the flag itself. This is cooperative cancellation: the thread decides where it's safe to stop.
Thread me = Thread.currentThread();
while (!me.isInterrupted()) {
crunchNextChunk();
}
cleanUp();Blocking methods react
sleep(), join() and wait() respond to an interrupt by throwing InterruptedException, immediately, even if the interrupt arrived *before* the call. And when they throw, they clear the flag. The exception now carries the request; the flag no longer does.
Your turn
main interrupts itself, then sleeps. What does this print?
Thread.currentThread().interrupt();
try {
Thread.sleep(5000);
System.out.println("woke up");
} catch (InterruptedException e) {
System.out.println("caught");
Thread.currentThread().interrupt();
}
System.out.println(
Thread.currentThread().isInterrupted());caught truecaught falsewoke up true
Show the answer
sleep() sees the pending interrupt and throws at once, which clears the flag. But the catch block restores it with interrupt(), so the last line prints true. Delete that line and it would print false.
Handling InterruptedException
} catch (InterruptedException e) {
log.warn("ignored");
}The flag was cleared and now the request is lost. A while (!isInterrupted()) loop never sees it, and pool shutdown hangs.
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}Restores the flag so code higher up the stack, like a thread pool shutting down, still sees the request.
interrupted() vs isInterrupted()
t.isInterrupted() just reads the flag. The static Thread.interrupted() reads and clears the current thread's flag. So calling Thread.interrupted() in a catch block does the opposite of restoring: it wipes the request. To restore, call Thread.currentThread().interrupt().
join(): wait for a finisher
t.join() blocks the caller until thread t has finished. It also guarantees the caller then sees everything t wrote. So here "B" is always appended after "A", and main prints AB.
StringBuilder sb = new StringBuilder();
Thread t = new Thread(() -> sb.append("A"));
t.start();
t.join(); // wait until t is done
sb.append("B"); // always after "A"
System.out.println(sb);Why your app won't shut down
ExecutorService.shutdownNow() cancels running tasks by interrupting their threads. A task that swallows InterruptedException ignores the request, so shutdown hangs. In containers this is a classic: the pod ignores the stop signal until the platform's grace period runs out and it gets killed hard.
Key takeaways
- interrupt() sets a flag; it never forcibly stops a thread
- sleep/join/wait throw InterruptedException and clear the flag
- Restore the flag if you swallow the exception
- t.join() waits until thread t has finished
Java 1.0 had Thread.stop(). It could leave objects half-updated, was deprecated in 1998, and since Java 20 it simply throws UnsupportedOperationException.
Practice questions
What does this print?
Thread.currentThread().interrupt();
try {
Thread.sleep(1000);
System.out.println("slept");
} catch (InterruptedException e) {
System.out.println("interrupted");
}
System.out.println(
Thread.currentThread().isInterrupted());- interrupted false
- interrupted true
- slept true
- slept false
Check your answer
interrupted false. sleep() sees the pending interrupt and throws immediately. Throwing InterruptedException clears the flag, so isInterrupted() then returns false.
What does this print?
StringBuilder sb = new StringBuilder();
Thread t = new Thread(() -> sb.append("A"));
t.start();
t.join();
sb.append("B");
System.out.println(sb);- AB
- BA
- A
- B
Check your answer
AB. join() blocks main until the worker has finished appending "A", so "B" always comes second. join() also makes the worker's write visible to main.