afterexecute 是最稳的统一处理入口,因它在任务线程中执行,可天然获取原始任务、真实耗时、未捕获异常,且覆盖 execute/submit 提交的所有任务,不侵入业务、不漏监控、上下文完整。

直接用 afterExecute 就能在线程池每个任务结束后,统一做变量处理和监控,不用改业务代码,也不依赖 Future 或 CompletableFuture。
为什么 afterExecute 是最稳的统一处理入口
它在任务线程中执行,天然拿到原始 Runnable/Callable、真实耗时、未捕获异常(包括被 Future.get() 吞掉的底层异常),而且不管你是用 execute() 还是 submit() 提交的任务,全都会触发。
- 不侵入业务:不需要给每个任务加 try-catch 或包装器
- 不漏监控:连 Runnable 中抛出的 RuntimeException、Callable 中未 get() 的 ExecutionException 原因都能捕获
- 上下文完整:能访问到当前执行线程、任务对象、异常堆栈、执行时长,适合做精准归因
统一变量处理:提取并透传任务上下文信息
很多任务需要共享请求 ID、租户标识、链路 traceId 等变量,但又不能让每个 Runnable 都手动传参。可以在 beforeExecute 中从 MDC 或 ThreadLocal 提前注入,在 afterExecute 中统一清理或落库。
- beforeExecute 里从父线程继承 MDC:
MDC.setContextMap(parentMdc.get()) - afterExecute 里记录关键字段:
log.info("task={} tenant={} duration={}ms", r.getClass().getSimpleName(), MDC.get("tenant"), elapsedMs) - 最后调用
MDC.clear(),避免 ThreadLocal 内存泄漏和脏数据污染下一个任务
轻量级监控上报:耗时统计 + 异常捕获 + 慢任务告警
监控不是越多越好,关键是把核心指标打准。afterExecute 是唯一能同时拿到“耗时”和“异常”的地方,适合作为监控埋点主入口。
- 用 ThreadLocal 存开始时间(
ThreadLocal.withInitial(System::nanoTime)),afterExecute 中计算差值并转成毫秒 - 异常非空时,直接上报到日志系统或监控平台(如 Prometheus Counter + Histogram)
- 对超时任务单独打 warn 日志,例如:
if (elapsedMs > 1000) log.warn("SLOW_TASK {} took {}ms, stack={}", r, elapsedMs, t)
必须避开的坑:钩子方法里别让异常逃逸
afterExecute 如果抛出未捕获异常,会导致执行该任务的 Worker 线程直接退出,线程池实际可用线程数减少——这不是功能问题,是稳定性事故。
- 所有日志、上报、外部调用都必须包 try-catch
- 异常处理逻辑本身也要有 fallback,比如上报失败就本地缓存 1 条,异步重试
- 禁止在钩子里调用可能阻塞或超时的外部服务(如远程 HTTP 请求),推荐走异步队列或批量聚合上报










