Java 线程池监控
下文笔者讲述java中线程池监控的简介说明,如下所示
实战代码:
方案二:自定义扩展线程池
线程池监控的缘由
线程池是Java并发开发的核心组件,
线程池的异常是高并发系统OOM、接口超时、
服务雪崩的高频诱因,未监控的线程池相当于"黑盒":
1.核心风险:线程池核心线程耗尽、任务队列堆满、
拒绝策略触发、线程阻塞/死锁、空闲线程过多浪费资源;
2.监控价值:提前发现线程池运行瓶颈、及时预警异常状态、
定位任务执行慢/堆积问题、优化线程池核心参数(核心线程数/队列长度等);
3.核心原则:线上运行的线程池,必须做监控,无监控不使用线程池。
线程池核心监控指标
基础运行状态指标(ThreadPoolExecutor 原生提供) 所有线程池核心指标,均来自 `java.util.concurrent.ThreadPoolExecutor` 原生get方法,无任何侵入性,所有线程池监控的基础。 // 1. 核心线程数(配置值) int corePoolSize = executor.getCorePoolSize(); // 2. 最大线程数(配置值) int maximumPoolSize = executor.getMaximumPoolSize(); // 3. 当前池中活跃的线程数(重要!) int activeCount = executor.getActiveCount(); // 4. 当前池中已创建的总线程数(含核心+非核心,空闲+活跃) int poolSize = executor.getPoolSize(); // 5. 任务队列的当前任务数/队列剩余容量(核心告警指标!) int queueSize = executor.getQueue().size(); int queueRemaining = executor.getQueue().remainingCapacity(); // 6. 线程池是否关闭/终止 boolean isShutdown = executor.isShutdown(); boolean isTerminated = executor.isTerminated();
任务全生命周期统计指标(ThreadPoolExecutor原生提供)
线程池内置了 3个原子性统计变量,
记录任务的「总提交数、总完成数、总拒绝数」,
是衡量线程池任务处理能力的核心指标,
全部是线程安全的原子统计,无并发问题:
//1. 【核心】提交给线程池的任务总数(包含:执行中+队列中+已完成+被拒绝)
long taskTotal = executor.getTaskCount();
//2.【核心】线程池已执行完成的任务总数
long taskCompleted = executor.getCompletedTaskCount();
//3. 【核心】被线程池拒绝的任务总数(告警核心指标,>0即代表异常!)
long taskRejected = executor.getLargestPoolSize(); // 补充:曾经创建过的最大线程数
补充:
任务堆积数 = 任务总数 - 已完成数 - 活跃线程数 ,该值>队列长度时,必然触发拒绝策略。
核心健康告警阈值(生产环境必配)
监控最终目的是「告警」,
给上述指标配置合理的阈值,
超过阈值立即告警,
以下是行业通用的生产级阈值标准,
直接复用:
1.活跃线程数 `activeCount` 持续等于
最大线程数 `maximumPoolSize` → 线程池满负载,处理能力不足;
2.任务队列数 `queueSize` 持续
大于队列容量的80% → 任务堆积,即将触发拒绝策略;
3.被拒绝任务数 `taskRejected > 0` → 严重异常,
必须立即处理(扩容线程池/扩容队列/优化任务);
4.已完成任务数增长缓慢 → 任务执行效率低(任务内有慢SQL/IO阻塞/死循环);
5.线程池总线程数 `poolSize` 长期等于核心线程数 →
线程池负载适中;若长期等于最大线程数 → 负载过高。
原生API基础监控(无侵入,最简,推荐所有线程池标配)
核心特点
- 无代码侵入、实现最简单、开发成本为0,所有线程池都应该加上该监控;
- 基于 `ThreadPoolExecutor` 原生的get方法获取所有指标;
- 支持:定时打印监控日志、暴露监控接口、对接告警系统;
- 注意:必须使用 ThreadPoolExecutor 类型声明线程池,
不能用 Executors 创建的线程池(Executors返回的是ExecutorService,无这些get方法)。
核心要求
// ❌ 错误:Executors创建的线程池,无法获取监控指标
ExecutorService executor = Executors.newFixedThreadPool(5);
// ✅ 正确:必须用ThreadPoolExecutor声明,自定义创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60L, TimeUnit.SECONDS, // 空闲线程存活时间
new LinkedBlockingQueue<>(100), // 任务队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
实战代码:
原生API定时监控线程池(生产可用)
通过 `ScheduledExecutorService` 实现定时打印线程池监控指标,
间隔可配置(生产一般配置10s/30s),
日志直接输出到控制台/日志文件,可对接ELK做日志分析和告警。
import java.util.concurrent.;
public class ThreadPoolNativeMonitor {
// 自定义线程池(生产环境推荐自定义,禁止用Executors)
private static final ThreadPoolExecutor BUSINESS_EXECUTOR = new ThreadPoolExecutor(
3, // corePoolSize 核心线程数
8, // maximumPoolSize 最大线程数
30L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(50), // 有界队列,必须用有界队列!
new ThreadFactory() {
int count = 1;
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("业务线程-" + count++);
return thread;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:由调用线程执行
);
public static void main(String[] args) {
// 1. 模拟提交任务
for (int i = 0; i < 60; i++) {
BUSINESS_EXECUTOR.submit(() -> {
try {
Thread.sleep(500); // 模拟业务执行
System.out.println(Thread.currentThread().getName() + " 执行任务");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 2. 启动定时监控线程池,每3秒打印一次监控指标
ScheduledExecutorService monitorExecutor = Executors.newSingleThreadScheduledExecutor();
monitorExecutor.scheduleAtFixedRate(() -> printThreadPoolMonitor(), 0, 3, TimeUnit.SECONDS);
}
/
核心方法:打印线程池所有原生监控指标
/
private static void printThreadPoolMonitor() {
ThreadPoolExecutor executor = BUSINESS_EXECUTOR;
System.out.println("============= 线程池监控指标 =============");
System.out.println("核心线程数:" + executor.getCorePoolSize());
System.out.println("最大线程数:" + executor.getMaximumPoolSize());
System.out.println("当前活跃线程数:" + executor.getActiveCount());
System.out.println("当前线程池总线程数:" + executor.getPoolSize());
System.out.println("任务队列当前任务数:" + executor.getQueue().size());
System.out.println("任务队列剩余容量:" + executor.getQueue().remainingCapacity());
System.out.println("已提交任务总数:" + executor.getTaskCount());
System.out.println("已完成任务总数:" + executor.getCompletedTaskCount());
System.out.println("历史创建的最大线程数:" + executor.getLargestPoolSize());
System.out.println("线程池是否关闭:" + executor.isShutdown());
System.out.println("=========================================");
}
}
方案二:自定义扩展线程池
(重写beforeExecute/afterExecute/terminated)【生产核心推荐 ✅】
核心特点
- 基于 `ThreadPoolExecutor` 的模板方法扩展,
重写3个钩子方法,无侵入、性能无损耗、
功能强大,是生产环境线程池监控的最优方案;
- 可实现:任务执行耗时统计、
任务异常捕获、任务开始/结束日志、
线程执行状态监控、自定义指标统计(如:平均执行耗时、最大执行耗时);
- 核心原理:ThreadPoolExecutor 内置3个空实现的钩子方法,
专门用于用户扩展监控,不会影响线程池的核心执行逻辑。
核心扩展的3个钩子方法(线程池的生命周期钩子)
所有方法都是 `protected` 修饰,直接继承 ThreadPoolExecutor 重写即可,线程安全,无需手动加锁:
// 1. 任务执行【前】调用:线程拿到任务,准备执行时触发
protected void beforeExecute(Thread t, Runnable r) { }
// 2. 任务执行【后】调用:任务执行完成/抛出异常时,都会触发(无论任务是否成功)
protected void afterExecute(Runnable r, Throwable t) { }
// 3. 线程池【终止】时调用:调用shutdown()并所有任务执行完成后触发,做资源释放/最终统计
protected void terminated() { }
生产级自定义监控线程池
实现功能:
✅ 统计每个任务的执行耗时;
✅ 捕获任务执行中的异常(线程池默认会吞掉任务异常,必须手动捕获);
✅ 统计:总执行耗时、最大执行耗时、最小执行耗时、平均执行耗时;
✅ 打印任务执行的全生命周期日志;
✅ 复用原生所有监控指标;
import java.util.concurrent.;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
/
生产级自定义监控线程池:继承ThreadPoolExecutor,重写钩子方法实现全维度监控
可直接复制到项目中使用,按需修改日志/告警逻辑
/
public class MonitorThreadPoolExecutor extends ThreadPoolExecutor {
// 自定义统计指标(原子类,线程安全)
private final AtomicLong totalTaskTime = new AtomicLong(0); // 所有任务总执行耗时
private final AtomicLong totalTaskCount = new AtomicLong(0); // 所有执行的任务数
private final AtomicReference<Long> maxTaskTime = new AtomicReference<>(0L); // 单任务最大耗时
private final AtomicReference<Long> minTaskTime = new AtomicReference<>(Long.MAX_VALUE); // 单任务最小耗时
// 构造方法:复用ThreadPoolExecutor的所有构造参数
public MonitorThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public MonitorThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
// ============ 核心:任务执行前 ============
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
// 给任务绑定【开始执行时间】,用ThreadLocal存储,线程隔离无并发问题
ThreadLocalHolder.START_TIME.set(System.currentTimeMillis());
System.out.printf("[线程池监控] 线程:%s 开始执行任务, 任务:%s%n", t.getName(), r.toString());
}
// ============ 核心:任务执行后 ============
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
long startTime = ThreadLocalHolder.START_TIME.get();
long endTime = System.currentTimeMillis();
long taskTime = endTime - startTime; // 单个任务执行耗时
// 1. 清除ThreadLocal,避免内存泄漏
ThreadLocalHolder.START_TIME.remove();
// 2. 捕获任务异常(重中之重!线程池默认吞异常,此处统一捕获)
if (t != null) {
System.err.printf("[线程池监控] 任务执行异常: %s, 异常信息: %s%n", r.toString(), t.getMessage());
}
// 3. 原子更新自定义统计指标
totalTaskTime.addAndGet(taskTime);
totalTaskCount.incrementAndGet();
// 更新最大耗时
while (true) {
long oldMax = maxTaskTime.get();
if (taskTime > oldMax && maxTaskTime.compareAndSet(oldMax, taskTime)) break;
else break;
}
// 更新最小耗时
while (true) {
long oldMin = minTaskTime.get();
if (taskTime < oldMin && minTaskTime.compareAndSet(oldMin, taskTime)) break;
else break;
}
// 4. 打印任务执行日志
System.out.printf("[线程池监控] 线程:%s 完成任务, 任务:%s, 执行耗时:%dms%n", Thread.currentThread().getName(), r.toString(), taskTime);
}
// ============ 核心:线程池终止时 ============
@Override
protected void terminated() {
super.terminated();
// 线程池关闭,打印最终统计指标
System.out.println("============= 线程池终止-最终监控统计 =============");
printCustomMonitor();
System.out.println("================================================");
}
// ============ 自定义监控指标打印 ============
public void printCustomMonitor() {
long totalTime = totalTaskTime.get();
long count = totalTaskCount.get();
double avgTime = count == 0 ? 0 : totalTime 1.0 / count;
System.out.println("自定义监控指标:");
System.out.println("所有任务总执行耗时:" + totalTime + "ms");
System.out.println("总执行任务数:" + count);
System.out.println("单任务最大执行耗时:" + maxTaskTime.get() + "ms");
System.out.println("单任务最小执行耗时:" + minTaskTime.get() + "ms");
System.out.println("单任务平均执行耗时:" + String.format("%.2f", avgTime) + "ms");
}
// ============ 复用原生监控指标打印 ============
public void printNativeMonitor() {
System.out.println("原生监控指标:");
System.out.println("核心线程数:" + getCorePoolSize());
System.out.println("最大线程数:" + getMaximumPoolSize());
System.out.println("当前活跃线程数:" + getActiveCount());
System.out.println("当前线程池总线程数:" + getPoolSize());
System.out.println("任务队列当前任务数:" + getQueue().size());
System.out.println("已提交任务总数:" + getTaskCount());
System.out.println("已完成任务总数:" + getCompletedTaskCount());
System.out.println("被拒绝任务数:" + (getTaskCount() - getCompletedTaskCount() - getQueue().size() - getActiveCount()));
}
// 存储任务开始时间的ThreadLocal
private static class ThreadLocalHolder {
private static final ThreadLocal<Long> START_TIME = new ThreadLocal<>();
}
// ============ 测试 ============
public static void main(String[] args) throws InterruptedException {
MonitorThreadPoolExecutor executor = new MonitorThreadPoolExecutor(
2, 5, 30L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 提交任务
for (int i = 0; i < 8; i++) {
int finalI = i;
executor.submit(() -> {
try {
Thread.sleep(finalI 100); // 模拟不同耗时的任务
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 定时打印监控指标
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
monitor.scheduleAtFixedRate(() -> {
executor.printNativeMonitor();
executor.printCustomMonitor();
System.out.println("----------------------------------------");
}, 0, 2, TimeUnit.SECONDS);
// 延时关闭线程池
Thread.sleep(5000);
executor.shutdown();
monitor.shutdown();
}
}
第三方工具监控(线上运维级,无代码侵入)
✅ 推荐1:Arthas(阿里开源,生产首选,必用) 核心优势 - 无代码侵入、无需重启应用、一键attach到JVM进程; - 专门提供线程池监控命令,查看所有线程池的详细指标; - 支持:实时查看线程池状态、任务队列、活跃线程、拒绝任务数、线程堆栈; - 适合:线上排查线程池堆积、阻塞、拒绝任务等问题。 核心命令(线程池监控专属) 1. 查看JVM中所有的线程池信息(核心命令,必用) threadpool 2. 查看指定线程池的详细信息(线程池名称/引用地址) threadpool -n 业务线程池 3. 查看线程池的线程堆栈,定位阻塞线程 thread -n 线程名
✅ 推荐2:JDK自带工具(无依赖,基础排查)
1. jstack:查看线程池的线程状态(RUNNABLE/WAITING/BLOCKED),
定位线程阻塞/死锁;
jstack [pid] > thread.log
关键字搜索:线程池的线程名(如`业务线程-1`),查看线程状态;
2. jvisualvm:JDK自带的可视化工具,集成了「线程池监控插件」,
可查看线程池的实时状态、任务队列、活跃线程数;
- 位置:JDK安装目录/bin/jvisualvm.exe
- 优势:可视化界面,直观查看线程池运行趋势。
✅ 推荐3:Spring Boot Actuator(Spring项目专属,微服务必用)
如果是Spring Boot/Spring Cloud项目,直接使用`spring-boot-starter-actuator`,
零开发成本,暴露线程池监控端点,配合Prometheus+Grafana实现:
1. 实时监控指标;
2. 可视化图表展示;
3. 配置告警规则(邮件/钉钉/短信);
核心依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置后访问:`http://ip:port/actuator/metrics` 即可查看线程池的所有指标。
线程池注意事项
✅ 避坑要点(重中之重,90%的人都会踩) 1. 禁止使用 Executors 创建线程池:返回的是ExecutorService,无监控指标,必须用ThreadPoolExecutor自定义创建; 2. 必须使用有界队列:无界队列(如LinkedBlockingQueue)会导致任务无限堆积,最终OOM,监控也无法预警; 3. 线程池的拒绝策略必须自定义:默认的AbortPolicy会抛出异常,线上建议用CallerRunsPolicy(调用线程执行),避免任务丢失; 4. 任务异常必须捕获:线程池会吞掉任务的unchecked异常,导致任务执行失败无日志,必须在afterExecute中捕获; 5. ThreadLocal必须清除:任务中使用ThreadLocal,必须在任务执行后remove,否则内存泄漏; 6. 监控指标不要频繁打印:高并发下,每秒打印监控日志会导致磁盘IO过高,建议间隔30s/1min打印一次。 ✅ 最佳实践(生产环境通用) 1. 线程池参数配置:核心线程数=CPU核心数2,最大线程数=CPU核心数4,队列长度=100~500(根据业务调整); 2. 监控组合策略:代码层(自定义线程池)+ 运维层(Arthas+Prometheus),双重保障; 3. 告警优先级:被拒绝任务数 > 队列满负载 > 活跃线程数满负载 > 任务执行耗时过长; 4. 线程池命名规范:给线程池的线程设置有意义的名称(如`订单处理线程-1`),便于排查问题; 5. 定期优化线程池参数:根据监控指标调整核心线程数/队列长度,比如任务堆积则扩容队列,拒绝任务则扩容最大线程数。
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。


