shutdown() 方法的作用是优雅地关闭线程池,它不会立即销毁线程池和其中的线程,而是:

- 停止接受新任务:调用
shutdown()后,再向线程池提交新任务(通过execute()或submit())会被拒绝,并抛出RejectedExecutionException。 - 完成已提交任务:线程池会继续处理那些已经提交到任务队列(
BlockingQueue)中的任务,直到所有任务都执行完毕。 - 终止空闲线程:当任务队列中的任务被处理完后,线程池会逐个终止(中断)那些空闲的(正在等待任务的)工作线程,直到所有线程都被销毁,线程池最终进入
TERMINATED状态。
shutdown() 的详细行为与生命周期
要完全理解 shutdown(),我们需要了解线程池的整个生命周期,一个线程池的状态转换如下:
RUNNING -> SHUTDOWN -> STOP -> TIDYING -> TERMINATED
- RUNNING: 线程池正在运行,可以接受新任务并处理队列中的任务。
- SHUTDOWN: 调用
shutdown()后进入此状态,不再接受新任务,但会处理完队列中的任务。 - STOP: 调用
shutdownNow()后进入此状态,不再接受新任务,并且会尝试中断正在执行的任务,同时清空任务队列。 - TIDYING: 当所有任务都已终止,并且工作线程数量为 0 时,线程池会进入此状态,此时会调用
terminated()钩子方法。 - TERMINATED:
terminated()方法执行完毕后,线程池进入最终状态TERMINATED。
shutdown() 的具体步骤:
- 状态转换:将线程池的状态从
RUNNING切换到SHUTDOWN。 - 中断空闲线程:调用
interruptIdleWorkers()方法,该方法会遍历所有工作线程,尝试中断那些没有在执行任务的线程(即正在从队列中获取任务的线程),正在执行任务的线程不受影响。 - 任务处理:线程池会继续从任务队列中获取并执行任务,直到队列为空。
- 线程销毁:当队列为空后,工作线程在尝试从队列获取任务时,会发现自己被中断(因为
shutdown()调用了interruptIdleWorkers),于是它们会退出run()方法,线程被销毁。 - 最终状态:当最后一个工作线程被销毁后,线程池的状态最终会变为
TERMINATED。
shutdown() 与 shutdownNow() 的关键区别
这是一个非常常见的面试点。shutdownNow() 是“暴力”关闭,而 shutdown() 是“温柔”关闭。
| 特性 | shutdown() |
shutdownNow() |
|---|---|---|
| 新任务 | 拒绝,抛出 RejectedExecutionException |
拒绝,抛出 RejectedExecutionException |
| 队列中的任务 | 执行完毕 | 不执行,并尝试将它们从队列中移除返回 |
| 正在执行的任务 | 继续执行,不受影响 | 尝试中断(通过调用 Thread.interrupt()),但不保证一定能中断 |
| 状态转换 | RUNNING -> SHUTDOWN -> TERMINATED |
RUNNING -> STOP -> TIDYING -> TERMINATED |
| 返回值 | void |
返回一个 List<Runnable>,包含队列中尚未执行的任务 |
简单记忆:
shutdown(): “等我忙完手头和队列里的活儿,我就下班了。”shutdownNow(): “全体都有!立刻停止手头的工作,马上解散!队列里的活儿也别干了。”
如何确保线程池已完全关闭?
shutdown() 是一个非阻塞方法,调用它只是启动了关闭流程,如果你需要在关闭流程完成后执行某些操作(释放资源、记录日志),你需要等待线程池真正终止。

Java 提供了两种方式来等待线程池终止:
awaitTermination(long timeout, TimeUnit unit)
这是最常用和推荐的方法,它会阻塞当前线程,直到线程池关闭完成,或者超时。
工作原理:
- 调用
shutdown()后,紧接着调用awaitTermination()。 awaitTermination()会定期(每 300ms 左右)检查线程池的状态是否为TERMINATED。- 如果是,则立即返回
true。 - 如果在指定的
timeout时间内,线程池仍未终止,则方法返回false。 - 如果等待过程中被中断,方法会抛出
InterruptedException。
最佳实践代码示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ShutdownExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
// 提交一些任务
for (int i = 0; i < 5; i++) {
executor.execute(() -> {
try {
// 模拟任务执行
System.out.println(Thread.currentThread().getName() + " is running...");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " has finished.");
} catch (InterruptedException e) {
// 线程被中断时打印信息
System.out.println(Thread.currentThread().getName() + " was interrupted.");
}
});
}
System.out.println("Shutting down the executor...");
// 1. 启动优雅关闭
executor.shutdown();
try {
// 2. 等待线程池终止,最多等待 5 秒
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
// 5 秒后仍未终止,可以采取进一步措施
System.out.println("Executor did not terminate in 5 seconds. Forcing shutdown.");
// executor.shutdownNow(); // 可以选择强制关闭
}
} catch (InterruptedException e) {
// 如果等待被中断,则重新设置中断状态,并尝试强制关闭
System.err.println("Await termination was interrupted.");
executor.shutdownNow();
// 保留中断状态
Thread.currentThread().interrupt();
}
System.out.println("Executor has been shut down completely.");
}
}
isTerminated()
这个方法的作用是检查线程池是否已经完全终止(即状态为 TERMINATED),它是一个非阻塞方法。
它会与一个循环和 awaitTermination 结合使用,或者用于在单独的线程中监控线程池状态。
// ... (接上面的代码)
executor.shutdown();
// 使用 isTerminated() 进行轮询检查(不推荐,会占用CPU)
while (!executor.isTerminated()) {
// 等待一会儿再检查
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Executor has been shut down completely (polled).");
为什么不推荐轮询 isTerminated()?
因为它会不断地消耗 CPU 资源来检查状态,而 awaitTermination() 在等待期间是让出 CPU 的,效率更高,也更优雅。
完整的优雅关闭流程总结
在实际应用中,一个完整的、健壮的线程池关闭流程通常如下:
- 收到关闭信号:应用关闭钩子被触发,或某个服务被停止。
- 调用
shutdown():开始优雅关闭流程,拒绝新任务。 - 调用
awaitTermination(long timeout, TimeUnit unit):等待线程池在合理的时间内完成关闭。 - 处理超时:
awaitTermination返回false(表示超时),说明有任务可能执行时间过长或线程卡住了,此时可以:- 记录警告日志。
- 调用
shutdownNow()尝试强制关闭。 - 或者根据业务需求决定是否等待更长时间。
- 处理中断:
awaitTermination抛出InterruptedException,说明等待线程被中断,此时应:- 恢复中断状态 (
Thread.currentThread().interrupt())。 - 调用
shutdownNow()来确保线程池被关闭。 - 处理中断逻辑。
- 恢复中断状态 (
遵循这个流程,可以确保你的应用程序在关闭时能够正确地释放资源,避免线程泄漏和任务丢失。
