Shutting Down Executors Correctly
An executor's worker threads are usually non-daemon threads, so a program that forgets to shut its executor down may never exit. Shutting down also has to be done carefully: you usually want tasks already submitted to finish, but you don't want to wait forever for a stuck one. Java gives you shutdown(), shutdownNow(), awaitTermination() and, since Java 19, close(). In this lesson, you will learn what each does and the standard way to combine them.
Methods
| Method | Description |
|---|---|
shutdown() | It is used to stop accepting new tasks; already-submitted tasks still run. |
shutdownNow() | It stops accepting tasks, interrupts running tasks, and returns the tasks that never started. |
awaitTermination(time, unit) | It waits until all tasks finish or the time runs out; returns true if finished. |
isShutdown() / isTerminated() | It reports whether shutdown has begun, and whether everything has finished. |
close() | It calls shutdown() and waits for all tasks (Java 19); used by try-with-resources. |
Example: shutdown vs shutdownNow
Let's see an example with a pool of one thread and three tasks.
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
public class Shutdowns {
static Runnable task(String name) {
return () -> {
try {
Thread.sleep(200);
System.out.println(name + " finished");
} catch (InterruptedException e) {
System.out.println(name + " interrupted");
}
};
}
public static void main(String[] args) throws InterruptedException {
ExecutorService gentle = Executors.newSingleThreadExecutor();
gentle.submit(task("navika-1"));
gentle.submit(task("navika-2"));
gentle.shutdown();
try {
gentle.submit(task("late"));
} catch (RejectedExecutionException e) {
System.out.println("New task rejected after shutdown");
}
gentle.awaitTermination(5, TimeUnit.SECONDS);
ExecutorService hard = Executors.newSingleThreadExecutor();
hard.submit(task("advitha-1"));
hard.submit(task("advitha-2"));
hard.submit(task("advitha-3"));
Thread.sleep(50);
List<Runnable> neverRan = hard.shutdownNow();
hard.awaitTermination(5, TimeUnit.SECONDS);
System.out.println("Tasks that never started: " + neverRan.size());
}
}New task rejected after shutdown
navika-1 finished
navika-2 finished
advitha-1 interrupted
Tasks that never started: 2Explanation: After shutdown(), the pool rejects new tasks with RejectedExecutionException but still runs both tasks already queued. shutdownNow() interrupts the running task (advitha-1) and returns the two queued tasks that never started. awaitTermination waits for the threads to finish in both cases.
The Recommended Pattern
pool.shutdown();
try {
if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
pool.shutdownNow();
if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
System.err.println("Pool did not terminate");
}
}
} catch (InterruptedException e) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}This lets tasks finish politely, then interrupts them if they take too long, and handles the case where the shutting-down thread is itself interrupted.
Common Mistakes
Never shutting down. The program hangs at the end.
Calling shutdownNow() on tasks that ignore interrupts. They keep running; write tasks that respond to interruption.
Calling awaitTermination without shutdown first. It just waits for the timeout.
Practice
- Easy: Submit three tasks, call
shutdown(), printisShutdown()andisTerminated(), then wait and print them again. - Harder: Write a helper method
closeQuietly(ExecutorService pool, Duration timeout)that implements the two-phase pattern.
Key Takeaways
Was this tutorial helpful?