可通过继承threadpoolexecutor重写beforeexecute和afterexecute方法精准监控任务耗时:beforeexecute用system.nanotime()记录开始时间并存入concurrenthashmap,afterexecute计算纳秒差并转毫秒,覆盖正常与异常执行场景,支持日志、指标上报及超时告警。

在 Java 线程池中,可以通过继承 ThreadPoolExecutor 并重写 beforeExecute 和 afterExecute 方法,实现对每个任务执行耗时的精准监控。
重写 beforeExecute 记录开始时间
该方法在线程即将执行任务前被调用,适合存入当前系统纳秒时间戳(System.nanoTime()),避免毫秒级精度不足。注意不能用 System.currentTimeMillis(),因它受系统时钟调整影响,可能导致负耗时。
- 将时间戳与任务对象关联,推荐使用
ThreadLocal或ConcurrentHashMap缓存(后者需注意 key 的唯一性,如用task.hashCode() + System.identityHashCode(task)组合) - 若任务可能被复用(如
Runnable实现类被多次提交),应确保每次提交生成独立标识,避免时间戳污染
重写 afterExecute 获取耗时并上报
该方法在任务执行完毕(无论正常结束或抛出异常)后被调用,是计算耗时和清理记录的合适位置。
Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要:(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设...
- 从缓存中取出对应开始时间,用
System.nanoTime() - startNanos得到纳秒级耗时,再转为毫秒(除以 1_000_000.0) - 可在此处打印日志、上报 Prometheus 指标、写入监控队列,或触发超时告警(如耗时 > 5000ms)
- 务必捕获并处理缓存未命中情况(例如任务被拒绝、线程池提前 shutdown),避免 NPE 或统计偏差
注意异常任务的完整覆盖
afterExecute 会接收两个参数:Runnable r 和 Throwable t。当任务抛出未捕获异常时,t 非 null;若任务正常完成,t 为 null。但即使发生异常,也要统计其实际执行时间——这正是钩子方法比 AOP 更可靠的原因。
- 不要仅在
t == null时才计算耗时 - 可额外记录异常类型(
t.getClass().getSimpleName())和消息摘要,便于根因分析 - 若使用
FutureTask或Callable,需注意beforeExecute接收的是包装后的FutureTask,不是原始Callable,但耗时统计逻辑不变
一个轻量可用的示例实现
无需引入外部依赖,只需扩展 ThreadPoolExecutor:
public class TimingThreadPoolExecutor extends ThreadPoolExecutor {
private final ConcurrentHashMap<object long> startTimeMap = new ConcurrentHashMap();
<pre class="brush:java;toolbar:false;">public TimingThreadPoolExecutor(int corePoolSize, int maxPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<runnable> workQueue) {
super(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
startTimeMap.put(r, System.nanoTime());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
Long start = startTimeMap.remove(r);
if (start != null) {
long duration = System.nanoTime() - start;
double ms = duration / 1_000_000.0;
String msg = String.format("Task %s took %.2f ms", r, ms);
if (t != null) {
msg += " — failed with " + t.getClass().getSimpleName();
}
System.out.println(msg); // 替换为你的日志框架或监控上报
}
}</runnable>
}
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










