Shutting Down Executors Correctly

Naveen AnamaneniUpdated · 3 min read

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

MethodDescription
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.

Shutdowns.java▶ Try it
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());
    }
}
Output
New task rejected after shutdown
navika-1 finished
navika-2 finished
advitha-1 interrupted
Tasks that never started: 2

Explanation: 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.

Two-phase shutdown
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

  1. Easy: Submit three tasks, call shutdown(), print isShutdown() and isTerminated(), then wait and print them again.
  2. Harder: Write a helper method closeQuietly(ExecutorService pool, Duration timeout) that implements the two-phase pattern.

Quick check: Shutting down executors

4 questions
1What does shutdown() do to tasks already queued?
2What does shutdownNow() return?
3What happens to a task submitted after shutdown()?
4What does close() do in Java 19+?

Key Takeaways

Was this tutorial helpful?

Lesson 416 of 482