线程池无法在JVM崩溃或OOM时保存未执行任务,必须依赖外部持久化机制与启动恢复策略:任务状态需落库、重启时扫描未完成任务重入线程池、配合幂等性与可观测性保障可靠性。

线程池本身无法在JVM崩溃或OOM时主动保存未执行任务——因为进程一旦退出,内存中所有状态(包括线程池队列、活跃线程、待处理任务)全部丢失。真正能“留存”任务的,不是线程池,而是**外部持久化机制与启动时恢复策略的组合**。
任务状态必须落库,不能只存在内存里
这是最核心的前提。只要任务还在内存队列中(比如 LinkedBlockingQueue 或 ArrayBlockingQueue),JVM一挂,数据就清零。
- 设计一张任务表,字段至少包含:
task_id(唯一标识)、status(如 INIT/PROCESSING/DONE/FAILED)、payload(序列化任务参数)、create_time、execute_time - 每次提交任务前,先插入数据库并设为
INIT;只有执行成功后,才更新为DONE - 避免用 UUID 做主键导致写入热点,可考虑雪花 ID 或业务自增逻辑
系统重启时自动捞取未完成任务重入线程池
JVM重启后,需主动扫描数据库中 status = 'INIT' 或 'PROCESSING'(配合超时判断)的任务,并重新提交到新初始化的线程池。
- 建议在 Spring Boot 的
@PostConstruct或 ApplicationRunner 中触发恢复逻辑 - 对
PROCESSING任务要加时间戳判断:比如last_update_time < now() - 5min,视为“疑似卡死”,按失败重试处理 - 恢复时注意幂等性:同一任务可能被多次恢复,payload 中应含去重键(如业务单号+操作类型)
OOM发生时尽量多留线索,辅助事后归因
虽然任务恢复靠数据库,但 OOM 本身需要可观测性支撑,否则下次还会挂——而频繁宕机会让恢复机制失效。
立即学习“Java免费学习笔记(深入)”;
- 启用
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dumps/,确保路径可写且有足够磁盘空间 - 搭配
-XX:OnOutOfMemoryError="sh /opt/app/bin/oom_notify.sh %p",脚本内快速记录 PID、时间、主机名,并触发告警(如企业微信/钉钉机器人) - 禁用无界队列:
new LinkedBlockingQueue()是高危操作,务必改用new ArrayBlockingQueue(200)等有界队列 - 拒绝策略别用
AbortPolicy默默丢任务,优先选CallerRunsPolicy或自定义策略写日志+报警
进阶:用消息队列替代内存队列
如果业务允许一定延迟和异步解耦,把“任务提交”变成“发消息”,天然具备崩溃存活能力。
- 用 Kafka/RocketMQ/RabbitMQ 替代线程池内部队列,生产者发消息即算提交成功
- 消费者端用线程池消费,但每条消息处理完再
ack,失败则重试或进死信队列 - 消息体仍需带唯一业务ID,确保下游幂等;消息中间件本身就成了任务状态中心


















