Java
Concurrency
Thread Management
shutdown
awaitTermination

shutdown and awaitTermination which first call have any difference?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Yes, the call order matters. The normal sequence is shutdown() first and awaitTermination() second, because awaitTermination() only waits for an executor that is already in the process of terminating or has already terminated.

What shutdown() Does

shutdown() tells the ExecutorService to stop accepting new tasks while allowing already submitted tasks to finish.

java
1ExecutorService executor = Executors.newFixedThreadPool(2);
2
3executor.submit(() -> {
4    try {
5        Thread.sleep(1000);
6        System.out.println("done");
7    } catch (InterruptedException e) {
8        Thread.currentThread().interrupt();
9    }
10});
11
12executor.shutdown();

After shutdown(), the executor is still running if tasks are in progress. It is simply moving toward termination.

What awaitTermination() Does

awaitTermination(timeout, unit) blocks the current thread until one of these things happens:

  • the executor terminates
  • the timeout expires
  • the waiting thread is interrupted

It does not initiate shutdown by itself.

java
boolean finished = executor.awaitTermination(2, TimeUnit.SECONDS);
System.out.println(finished);

If you call awaitTermination() before shutdown(), the executor is usually still active and not even trying to terminate. In that case, the wait often lasts until the timeout expires and returns false.

The Correct Order

The common pattern is:

java
1import java.util.concurrent.ExecutorService;
2import java.util.concurrent.Executors;
3import java.util.concurrent.TimeUnit;
4
5public class Demo {
6    public static void main(String[] args) {
7        ExecutorService executor = Executors.newFixedThreadPool(2);
8
9        executor.submit(() -> {
10            try {
11                Thread.sleep(1000);
12                System.out.println("task finished");
13            } catch (InterruptedException e) {
14                Thread.currentThread().interrupt();
15            }
16        });
17
18        executor.shutdown();
19
20        try {
21            if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
22                executor.shutdownNow();
23            }
24        } catch (InterruptedException e) {
25            executor.shutdownNow();
26            Thread.currentThread().interrupt();
27        }
28    }
29}

This is the practical sequence:

  1. stop accepting new tasks
  2. wait for current tasks to finish
  3. force interruption only if the graceful wait fails

What Happens If You Reverse The Calls

Suppose you do this:

java
executor.awaitTermination(5, TimeUnit.SECONDS);
executor.shutdown();

That is usually pointless. awaitTermination() is waiting for a termination process that has not started yet. Unless some other thread already called shutdown() or shutdownNow(), the executor has no reason to terminate.

So yes, the order makes a real difference.

There is one narrow exception: if another part of the program already initiated shutdown, then awaitTermination() can be called by a different thread to wait for completion. But in the normal single-owner pattern, shutdown() should come first.

shutdown() Versus shutdownNow()

It is also important not to confuse shutdown() with shutdownNow().

  • 'shutdown() is orderly and lets submitted tasks finish.'
  • 'shutdownNow() tries to interrupt running tasks and returns tasks that never started.'

A common lifecycle is graceful first, forceful second:

java
1executor.shutdown();
2if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
3    executor.shutdownNow();
4}

That sequence gives tasks a chance to complete cleanly while still preventing the application from hanging forever.

Why The Pattern Includes Interrupted Handling

If the waiting thread is interrupted while blocked in awaitTermination(), the method throws InterruptedException. Good code restores the interrupt flag after doing cleanup.

java
1catch (InterruptedException e) {
2    executor.shutdownNow();
3    Thread.currentThread().interrupt();
4}

Failing to restore the interrupt flag can hide cancellation signals from higher-level code.

A Small Timing Example

Here is a simple program that shows the behavior clearly.

java
1import java.util.concurrent.*;
2
3public class TimingDemo {
4    public static void main(String[] args) throws Exception {
5        ExecutorService executor = Executors.newSingleThreadExecutor();
6
7        executor.submit(() -> {
8            try {
9                Thread.sleep(1500);
10            } catch (InterruptedException e) {
11                Thread.currentThread().interrupt();
12            }
13        });
14
15        System.out.println(executor.awaitTermination(1, TimeUnit.SECONDS));
16        executor.shutdown();
17        System.out.println(executor.awaitTermination(3, TimeUnit.SECONDS));
18    }
19}

The first wait typically prints false because the executor is still active and has not been told to terminate. The second wait can print true because shutdown has begun and the task finishes within the timeout.

Common Pitfalls

  • Calling awaitTermination() before shutdown() and expecting it to initiate shutdown.
  • Using shutdownNow() immediately when a graceful shutdown would be sufficient.
  • Ignoring InterruptedException instead of restoring the interrupt flag.
  • Submitting more tasks after shutdown() and being surprised by rejection.
  • Forgetting that awaitTermination() can return false after timing out.

Summary

  • The normal order is shutdown() first, then awaitTermination().
  • 'awaitTermination() waits for termination; it does not start termination.'
  • Calling awaitTermination() first usually just waits until timeout.
  • A common pattern is graceful shutdown followed by shutdownNow() only if needed.
  • Handle interruption properly by restoring the thread's interrupt status.

Course illustration
Course illustration

All Rights Reserved.