核心是安全落库+可重试+不阻塞主线程:拒绝策略中校验后提取任务关键字段json化,异步写入数据库;表含id/内容/状态/重试次数/时间;配定时扫描+幂等重试机制。

Java 线程池配合自定义拒绝策略将任务持久化到数据库,核心不是“一拒就写库”,而是“安全落库 + 可重试 + 不拖慢主线程”。直接在 rejectedExecution 方法里同步执行 JDBC 插入,会卡住上游调用线程,导致接口超时甚至雪崩。
关键前提:任务数据必须可提取、可序列化
Runnable 本身通常不可序列化,也不该直接存对象引用。稳妥做法是:
- 定义带业务语义的自定义任务类(如
OrderProcessTask),封装 ID、操作类型、参数 JSON 字符串等关键字段 - 在拒绝策略中做类型判断:
if (r instanceof OrderProcessTask),再调用getOrderId()、toParamJson()等方法提取结构化数据 - 避免使用
r.toString()或new Gson().toJson(r)直接序列化整个 Runnable —— 容易 NPE 或含敏感引用
拒绝策略实现:只组装、不阻塞、有兜底
自定义类实现 RejectedExecutionHandler,但 rejectedExecution 方法内只做三件事:
- 空值与线程池状态校验:
if (r == null || executor.isShutdown()) return; - 提取关键字段并转成 JSON 字符串(如 Jackson
writeValueAsString()) - 投递到异步通道:调用 Spring 的
@Async方法,或放入内存队列(如LinkedBlockingQueue)由独立消费者线程处理
不推荐在该方法里直接调用 DAO 层;若非要同步写,必须加超时控制(如 HikariCP 连接超时 + 查询超时),失败时降级为本地文件日志,防止全链路阻塞。
数据库表与补偿机制必须配套
光存进去不算完,得有人“捡起来重跑”。表结构至少包含:
-
id VARCHAR(36)(主键,建议 UUID) -
task_data TEXT(JSON 格式,含可还原的业务参数) -
status VARCHAR(20)(如 WAITING / SUCCESS / FAILED) -
retry_count INT DEFAULT 0(防无限重试) -
create_time DATETIME和update_time DATETIME
配套一个定时任务(如 @Scheduled(fixedDelay = 30_000)),扫描 status = 'WAITING' 且 retry_count 的记录,重新提交到线程池;成功则更新状态为 SUCCESS,失败则 <code>retry_count++,达上限后设为 FAILED 并触发人工告警。
幂等与事务边界要清晰
重试提交时,不能重复执行同一业务逻辑。常见做法:
- 任务 JSON 中带唯一业务 ID(如订单号),执行前先查 DB 或 Redis 判断是否已处理
- 数据库插入用
INSERT IGNORE或ON DUPLICATE KEY UPDATE防止重复建模 - 不要在 DAO 层事务里调用线程池 submit(),避免事务未提交就触发新任务——应先 commit 再异步触发
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











