绝大多数场景应先调用 shutdown() 实现温和收尾,等待任务自然结束;超时未终止再用 shutdownNow() 强制中断,且需确保任务响应中断、正确处理 InterruptedException 并重置中断状态。

shutdown() 和 shutdownNow() 到底该用哪个
绝大多数场景下,应该先调用 shutdown(),而不是一上来就 shutdownNow()。前者是“温和收尾”:不再接受新任务,但会等正在执行的任务自然结束;后者是“强制中断”,会尝试停止所有正在运行的线程(通过 Thread.interrupt()),但不保证成功——尤其当任务忽略中断或处于阻塞 I/O 中时,线程可能继续运行。
常见错误是把 shutdownNow() 当作“立刻释放资源”的银弹,结果日志里反复出现 RejectedExecutionException 或线程迟迟不退出。
- 用
shutdown()后,应配合awaitTermination(long, TimeUnit)等待合理时间(比如 30 秒) - 若超时仍未终止,再考虑
shutdownNow()做兜底 - 永远不要在未调用
shutdown()或shutdownNow()的情况下让线程池变量被 GC —— 它不会自动关闭,线程会持续存活
为什么 awaitTermination() 经常返回 false
不是代码写错了,而是你没处理好任务本身的可中断性。线程池能终止的前提,是所有工作线程上的任务都已退出。如果某个任务在死循环中没检查 Thread.currentThread().isInterrupted(),或在 Object.wait()、Thread.sleep() 外部忽略了 InterruptedException,它就不会响应 shutdownNow() 的中断信号,导致 awaitTermination() 超时返回 false。
典型陷阱:
立即学习“Java免费学习笔记(深入)”;
- 使用
while (true)而非while (!Thread.currentThread().isInterrupted()) - 捕获了
InterruptedException却没重设中断状态(忘了调用Thread.currentThread().interrupt()) - 在
try-catch中吞掉异常,且未做任何退出逻辑
while (!Thread.currentThread().isInterrupted()) {
try {
// 执行业务逻辑
doWork();
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 必须重置,否则下次 isInterrupted() 返回 false
break;
}
}
ExecutorService 关闭后还能提交任务吗
不能。一旦调用了 shutdown() 或 shutdownNow(),再调用 execute() 或 submit() 都会抛出 RejectedExecutionException。这不是 bug,是设计使然——线程池进入 SHUTDOWN 或 STOP 状态后,内部状态已锁定。
所以务必确保所有任务提交都在关闭逻辑之前完成。常见误操作:
- 在异步回调里偷偷往已关闭的线程池提交新任务
- 用 Spring @PreDestroy 注解关闭线程池,但其他 Bean 的销毁顺序不确定,导致仍有组件试图提交任务
- 把线程池作为静态字段,生命周期脱离应用上下文管理
Spring 环境下怎么安全关闭自定义线程池
别手动在 @PreDestroy 里只调 shutdown() 就完事。Spring 的 ThreadPoolTaskExecutor 已封装好优雅关闭逻辑,推荐直接使用它,并设置关键属性:
-
setWaitForTasksToCompleteOnShutdown(true):启用等待,避免强制中断 -
setAwaitTerminationSeconds(30):指定最大等待秒数,超时后自动 fallback 到shutdownNow()
如果你非要用原生 ThreadPoolExecutor,记得在 @PreDestroy 方法里完整走一遍 shutdown → awaitTermination → shutdownNow 流程,并捕获可能的中断异常,否则容器关闭时线程池可能卡住。
最易被忽略的一点:线程池里的线程默认是 non-daemon 的,只要有一个没退出,JVM 就不会真正退出。这点在单元测试或短生命周期应用中特别容易暴露问题。










