Java 线程池提供了两个关闭方法:shutdown() 和 shutdownNow()。

shutdown():“优雅关闭”,它会停止接收新任务,但会等待正在执行的任务和队列中等待的任务执行完毕,之后,线程池才会真正关闭。shutdownNow():“立即关闭”,它会尝试停止所有正在执行的任务,并清空任务队列,返回队列中未执行的任务列表,它会尝试中断正在运行的线程(如果任务没有正确处理中断,则可能无效)。
详细讲解
shutdown() 方法
shutdown() 的执行流程如下:
- 停止接收新任务:调用
shutdown()后,如果你再尝试通过execute()方法提交新任务,线程池会拒绝该任务,并抛出RejectedExecutionException异常。 - 继续处理已有任务:线程池会继续处理以下任务:
- 已经开始执行的任务(
isShutdown()返回true,但isTerminated()返回false)。 - 任务队列中等待执行的任务。
- 已经开始执行的任务(
- 等待终止:线程池会一直等待,直到所有任务都执行完毕。
- 状态变更:当所有任务都执行完毕后,线程池的状态会变为
TERMINATED,isTerminated()返回true。
shutdown() 的特点:
- 非阻塞方法:
shutdown()方法会立即返回,它不会等待所有任务完成。 - 状态变化:调用后,线程池状态从
RUNNING变为SHUTDOWN。
shutdownNow() 方法
shutdownNow() 的执行流程如下:
- 停止接收新任务:与
shutdown()一样,它会拒绝新提交的任务。 - 尝试中断正在执行的任务:它会向线程池中所有正在执行任务的线程发送一个中断信号(
Thread.interrupt())。注意:这取决于你的任务代码是否正确地响应了中断,如果任务代码中忽略了中断,那么任务可能会继续执行。 - 清空任务队列:它会从任务队列中移除并返回所有尚未开始执行的任务。
- 立即终止:线程池会立即尝试停止所有工作,不再等待。
shutdownNow() 的特点:

- 非阻塞方法:同样会立即返回。
- 状态变化:调用后,线程池状态从
RUNNING或SHUTDOWN变为STOP。 - 返回值:返回一个
List<Runnable>,包含了队列中所有被移除的任务。
线程池状态流转图
理解线程池的状态是掌握 shutdown 的关键。ThreadPoolExecutor 使用一个 ctl 变量来控制线程池的运行状态,它是一个原子整数,高3位表示状态,其余位表示 worker 数量。
状态流转如下:
RUNNING -> SHUTDOWN -> TIDYING -> TERMINATED
| |
| |
|<--------|
|
v
STOP -> TIDYING -> TERMINATED
- RUNNING:可接收新任务,可处理队列中的任务。
- SHUTDOWN:不可接收新任务,但可处理队列中的任务,调用
shutdown()后进入此状态。 - STOP:不可接收新任务,不处理队列中的任务,并尝试中断正在执行的任务,调用
shutdownNow()后进入此状态。 - TIDYING:当所有任务已终止,
worker数量为0时,线程池会进入此状态,此时会执行terminated()钩子方法。 - TERMINATED:
terminated()方法执行完毕后,线程池最终进入此状态。
最佳实践:如何正确关闭线程池?
在实际开发中,我们通常不只用 shutdown() 或只用 shutdownNow(),而是将它们结合使用,并配合 awaitTermination() 来实现一个“优雅且超时”的关闭策略。
推荐的关闭模式:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolShutdownExample {
public static void main(String[] args) throws InterruptedException {
// 创建一个固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(4);
// 模拟提交一些任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
try {
// 模拟任务执行时间
System.out.println("Task " + taskId + " is running on " + Thread.currentThread().getName());
Thread.sleep(2000); // 每个任务执行2秒
} catch (InterruptedException e) {
// 如果任务被中断,打印信息并退出
System.out.println("Task " + taskId + " was interrupted.");
Thread.currentThread().interrupt(); // 恢复中断状态
}
System.out.println("Task " + taskId + " finished.");
});
}
// --- 开始关闭线程池 ---
// 第一步:停止接收新任务
executor.shutdown();
// 第二步:等待任务完成,并设置超时时间
// 这是一种更优雅的方式,它会在指定时间内等待任务完成
try {
// 等待60秒,直到所有任务执行完毕
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
// 如果超时后仍有任务未完成,则强制关闭
System.err.println("线程池未在60秒内关闭,将尝试强制关闭...");
List<Runnable> droppedTasks = executor.shutdownNow();
System.err.println("强制关闭,还有 " + droppedTasks.size() + " 个任务未被执行。");
// 再次等待,防止任务被卡住
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("线程池未能完全关闭。");
}
}
} catch (InterruptedException e) {
// 如果在等待过程中,当前线程被中断,则中断等待,并尝试强制关闭线程池
System.err.println("在等待线程池关闭时被中断,将尝试强制关闭...");
executor.shutdownNow(); // 取消正在执行的任务
// 重新设置中断状态,以便上层调用者感知
Thread.currentThread().interrupt();
}
System.out.println("线程池已成功关闭。");
}
}
代码解析:
executor.shutdown():首先调用shutdown(),进入“优雅关闭”模式,不再接受新任务,但会处理完队列中的任务。executor.awaitTermination(60, TimeUnit.SECONDS):这是关键一步,它会阻塞当前线程,直到线程池关闭(所有任务执行完毕)或者超时。- 如果在60秒内,所有任务都顺利完成了,
awaitTermination返回true,程序继续执行。 - 如果60秒后,仍有任务未完成,
awaitTermination返回false。
- 如果在60秒内,所有任务都顺利完成了,
- 超时后的处理 (
if (!executor.awaitTermination(...))):如果超时,说明有任务可能执行时间过长或被阻塞,我们调用executor.shutdownNow()进行“强制关闭”,尝试中断所有任务并清空队列。 InterruptedException的处理:如果在调用awaitTermination时,主线程被中断(另一个线程调用了main线程的interrupt()),awaitTermination会抛出InterruptedException,我们也应该调用shutdownNow()来清理线程池,并且恢复当前线程的中断状态。
| 方法 | 行为 | 状态变化 | 阻塞性 | 返回值 | 适用场景 |
|---|---|---|---|---|---|
shutdown() |
停止接收新任务,处理完已有任务后关闭。 | RUNNING -> SHUTDOWN -> TERMINATED |
非阻塞 | void |
希望任务能完整执行,不关心关闭耗时。 |
shutdownNow() |
停止接收新任务,尝试中断并停止所有任务,清空队列。 | RUNNING/SHUTDOWN -> STOP -> TERMINATED |
非阻塞 | List<Runnable> (未执行任务) |
需要立即释放资源,不关心任务是否完成。 |
awaitTermination() |
阻塞当前线程,直到线程池关闭或超时。 | 无 | 阻塞 | boolean (是否因超时返回) |
必须与 shutdown() 或 shutdownNow() 配合使用,是实现“优雅超时关闭”的核心。 |
核心要点:
- 先关后等:正确的关闭顺序是
shutdown()->awaitTermination()。 - 超时机制:
awaitTermination提供了超时控制,避免了线程池无限期等待。 - 强制关闭:超时后,
shutdownNow()是最后的手段,用于强制释放资源。 - 中断处理:任务代码必须正确处理中断信号,
shutdownNow()才能真正中断任务。
