Card 05/ 09

WalkthroughDifficulty: Advanced1 min

Shutting a Pool Down Without Losing the Work Still in It

A pool's threads are non-daemon by default, so a pool nobody shut down keeps the process alive forever. Shutting one down properly is four calls, and skipping two of them is the usual bug.

From a running pool to a process that can exit

Step 1 of 4

Stop accepting new work

java
pool.shutdown();

This returns immediately. It refuses further submissions and lets everything already queued run to completion — so calling it is not waiting, and the tasks are still going.

Wait for the queue to drain

java
boolean finished = pool.awaitTermination(30, TimeUnit.SECONDS);

This is the call that waits, and it returns whether everything finished inside the deadline. Without it, the next line runs while tasks are still going.

Interrupt whatever is left

java
if (!finished) {
    List<Runnable> never = pool.shutdownNow();
    log.warn("{} tasks never started", never.size());
}

shutdownNow interrupts the running tasks and hands back the ones still queued. It is a request rather than a guarantee: a task that ignores interruption carries on regardless.

Wait once more, and give up honestly

java
if (!pool.awaitTermination(10, TimeUnit.SECONDS)) {
    log.error("pool did not stop");
}

The second wait is what tells you whether the interrupt worked. A pool that is still running here holds a task that does not respond to interruption, and that is a bug in the task rather than in the shutdown.

The run to remember: shutdown asks, awaitTermination waits, shutdownNow insists. Since Java 19 an ExecutorService is AutoCloseable, so a try-with-resources does the polite half of this for you.