题目
如何优雅关闭线程池并处理未执行任务?
信息
- 类型:问答
- 难度:⭐⭐
考点
线程池关闭机制,任务中断处理,资源清理,异常处理
快速回答
优雅关闭线程池的关键步骤:
- 调用
shutdown()拒绝新任务并继续执行队列任务 - 使用
awaitTermination()等待任务完成 - 必要时调用
shutdownNow()中断任务 - 处理未执行任务(记录/重试/补偿)
- 捕获并处理
InterruptedException
任务代码需响应中断检查,实现Runnable或Callable时应在循环中检查Thread.interrupted()。
解析
原理说明
线程池关闭涉及两个核心方法:shutdown()平滑关闭(执行完队列任务)和shutdownNow()立即关闭(返回未执行任务列表并发送中断信号)。任务需响应中断才能正确终止,否则可能导致线程泄漏或资源未释放。
代码示例
ExecutorService pool = Executors.newFixedThreadPool(4);
// 提交任务
for (int i = 0; i < 10; i++) {
pool.submit(() -> {
while (!Thread.interrupted()) {
// 模拟工作(响应中断检查)
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重置中断状态
break;
}
}
});
}
// 优雅关闭流程
try {
pool.shutdown(); // 步骤1:拒绝新任务
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { // 步骤2:等待60秒
List<Runnable> unfinished = pool.shutdownNow(); // 步骤3:强制中断
// 步骤4:处理未执行任务
System.out.println("未完成任务数: " + unfinished.size());
unfinished.forEach(task ->
System.out.println("任务补偿: " + task.toString())
);
// 再次等待终止
if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
System.err.println("线程池未完全终止");
}
}
} catch (InterruptedException e) { // 步骤5:处理中断异常
pool.shutdownNow();
Thread.currentThread().interrupt();
}最佳实践
- 关闭顺序:先
shutdown()再shutdownNow(),避免直接强制关闭 - 超时设置:根据业务场景合理设置
awaitTermination超时时间 - 中断处理:任务中需周期性检查
Thread.interrupted(),并在捕获InterruptedException后恢复中断状态 - 资源清理:使用
try-finally确保任务占用的资源(如数据库连接)被释放
常见错误
- 忽略中断异常:仅捕获
InterruptedException而不处理,导致线程无法终止 - 未重置中断状态:捕获中断后未调用
Thread.currentThread().interrupt() - 双重关闭:重复调用
shutdownNow()可能抛出NullPointerException - 未处理未执行任务:导致业务逻辑缺失(如未持久化任务状态)
扩展知识
- 钩子函数:通过
Runtime.getRuntime().addShutdownHook()注册JVM关闭时的清理逻辑 - 线程池状态:
isShutdown()(关闭中)与isTerminated()(完全终止)的区别 - 定制线程池:使用
ThreadPoolExecutor构造函数定制RejectedExecutionHandler处理关闭时的任务提交 - Spring场景:在Spring Boot中可通过
@PreDestroy注解实现Bean销毁时的线程池关闭