线程池任务溢出时应异步持久化关键任务信息至数据库或结构化日志,避免阻塞主线程;表结构需含id、task_data等字段并设默认值;配套定时重试与幂等机制确保可靠执行。

线程池任务溢出时,靠默认拒绝策略(比如 AbortPolicy)直接抛异常,既丢数据又难追溯。真正稳妥的做法是:在拒绝发生时,把关键任务信息安全地记进日志或数据库——不是简单打印一行,而是确保不拖慢主线程、字段可还原、后续能重试。
为什么不能在 rejectedExecution 里直接写库?
因为 rejectedExecution 是在提交任务的线程中同步调用的。如果这里执行耗时的 JDBC 插入或网络日志上传,会卡住上游接口响应,造成雪崩式延迟。必须解耦。
- 推荐用异步方式落库:比如 Spring 的
@Async方法,或投递到内存队列(如LinkedBlockingQueue)由独立线程消费 - 若用异步,需配超时和失败兜底:比如插入失败时降级为本地文件日志,避免全链路丢失
- 不要序列化整个
Runnable对象:它通常不可序列化,且含大量无关引用;只提取业务 ID、操作类型、参数 JSON 字符串等关键字段
记录到数据库的关键设计点
存库不是为了“有”,而是为了“能用”。一张表要支撑后续扫描、重试、人工核查。
- 表结构至少包含:
id(唯一标识)、task_data(JSON 格式的关键参数)、retry_count(初始为 0)、status(如 WAITING/FAILED/SUCCESS)、create_time - 实体类建议用 Lombok 简化,字段全部设默认值,避免空指针;
task_data存字符串而非二进制,方便排查和 SQL 查询 - 插入操作必须包在
try-catch中:数据库连不上或主键冲突时,不能让异常穿透出去影响主线程
记录到日志的实用做法
日志适合快速落地、开发调试或低频补偿场景,但要注意可检索性和结构化。
立即学习“Java免费学习笔记(深入)”;
- 别只写
r.toString():内容不可靠,可能为空或泄露敏感信息;应统一提取字段后格式化为 JSON 日志,例如:{"taskId":"ORD-123","action":"payConfirm","params":{"amount":99.99}} - 日志级别设为
WARN或ERROR,并打上明确 tag(如[REJECTED_TASK]),方便 ELK 或 Grafana 过滤告警 - 配合异步日志框架(如 Logback 的 AsyncAppender),避免 I/O 阻塞主线程
配套机制不能少
光存进去没用,得有人把它捞出来再跑一遍。
- 定时任务每 30 秒扫描一次
status = 'WAITING' AND retry_count 的记录,重新提交到原线程池 - 重试成功则更新状态为 SUCCESS;失败则
retry_count++,达到上限(如 3 次)改为 FAILED,并触发企业微信/钉钉告警 - 所有重试逻辑必须幂等:比如根据订单 ID 去重,避免重复扣款或发券


















