Card 05/ 09
Shutting Down an ExecutorService Without Losing Work
shutdown asks, awaitTermination waits, and shutdownNow insists, in that order.
An ExecutorService has to be shut down, because its threads are non-daemon by default (ordinary threads, which keep the process alive), and a pool nobody shuts down keeps the process alive forever. A complete shutdown is four calls: stop taking work, wait, interrupt what is left, and wait again.
You need a pool with tasks still queued or running, a deadline you can afford to wait, and to know how your tasks respond to an interrupt, the request to stop that shutdownNow sends and that blocking calls answer with InterruptedException, because that decides whether the third step works.
From a running pool to a process that can exit
Stop accepting new work
pool.shutdown();shutdown() returns immediately. It refuses further submissions and lets everything already queued run to completion, so calling it is not waiting: the tasks are still going.
Wait for the queue to drain
boolean finished = pool.awaitTermination(30, TimeUnit.SECONDS);This is the call that waits, and it returns whether everything finished within the deadline. Without it, the next line runs while tasks are still going.
Interrupt whatever is left
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, not a guarantee: a task that ignores interruption carries on regardless.
Wait once more, and report the outcome
if (!pool.awaitTermination(10, TimeUnit.SECONDS)) {
log.error("pool did not stop");
}The second wait tells you whether the interrupt worked. A pool still running at this point holds a task that does not respond to interruption, which is a bug in the task rather than in the shutdown.
You know it worked when the second awaitTermination returns true and the process exits. Since Java 19, an ExecutorService is AutoCloseable, so a try-with-resources block does the first two steps for you.
awaitTermination throws InterruptedException if the thread doing the waiting is itself interrupted. The usual response, from the ExecutorService documentation, is to call shutdownNow() and then restore the interrupt with Thread.currentThread().interrupt(), so the code above yours still sees that it was asked to stop.
← → move through sections and cards