Java线程池的监控
大家好,我是欧阳方超,公众号同名。

1 概述
可监控的线程池是指在实际生产环境中,对ThreadPoolExecutor或其子类进行增强,让开发/运维人员能够实时感知线程池的运行健康状态、及时发现问题、进行告警,并支持后续调优的线程池实现。
2 JDK原生线程池状态获取
其实,JDK提供的ThreadPoolExecutor自带了丰富的状态查询API。要想监控它,只需要定时去获取这些数据即可。可获取到的核心指标包括:
- getPoolSize():当前线程池中的实际线程数。
- getActiveCount():当前正在执行任务的线程数(最核心的负载指标)。
- getQueue().size():当前排队等待执行的任务数(积压指标,决定是否会触发拒绝策略)。
- getCompletedTaskCount():已完成的任务总数。
2.1 轻量级监控器实现
这里实现一个轻量级监控器,实现思路是通过开启一个后台定时任务(ScheduledExecutorService),每隔几秒拉取一次核心业务线程池的状态并打印到日志中。
import java.util.concurrent.*;
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
//1.初始化核心业务线程池
ThreadPoolExecutor orderPool = new ThreadPoolExecutor(
5,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
//2.启动一个监控线程,定时打印监控指标
ScheduledExecutorService monitorThread = Executors.newSingleThreadScheduledExecutor();
monitorThread.scheduleAtFixedRate(() -> {
System.out.printf("[监控采集] 核心线程数:%d,活动线程数:%d,最大线程数:%d,队列中的任务数:%d,已完成任务数:%d%n",
orderPool.getCorePoolSize(),
orderPool.getActiveCount(),
orderPool.getMaximumPoolSize(),
orderPool.getQueue().size(),
orderPool.getCompletedTaskCount()
);
}, 0, 2, TimeUnit.SECONDS);
//3.模拟并发提交任务
for (int i = 0; i < 20; i++) {
orderPool.execute(() -> {
try {
Thread.sleep(1000);//模拟任务耗时
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
}
}
上面的程序中,首先创建了一个核心业务线程池orderPool,接着创建了一个用于监控orderPool线程池指标的监控线程,该线程每两秒输出一次orderPool线程池的核心线程数、活动线程数等指标,接着主线程让orderPool线程池中的线程执行20个任务,程序的输出如下:
[监控采集] 核心线程数:5,活动线程数:0,最大线程数:10,队列中的任务数:0,已完成任务数:0
[监控采集] 核心线程数:5,活动线程数:5,最大线程数:10,队列中的任务数:10,已完成任务数:5
[监控采集] 核心线程数:5,活动线程数:5,最大线程数:10,队列中的任务数:0,已完成任务数:15
[监控采集] 核心线程数:5,活动线程数:0,最大线程数:10,队列中的任务数:0,已完成任务数:20
注意,上面的程序并不会停止,即使orderPool执行完了20个任务,因为所创建的orderPool、monitorThread都是非守护线程,即用户线程,jvm会等待所有非守护进程结束后退出,如果想让上面的程序优雅结束,可以这样调整,把monitorThread设置为守护线程,在for循环后面加一行代码,关闭orderPool,即orderPool.shutdown(),关闭之后orderPool不再接收新任务,但会把正在执行的任务以及队列中等待的任务全部执行完毕,即会保证那20个任务执行完毕,此时jvm进程中只剩monitorThread一个守护线程了,而jvm又不会等待守护线程执行结束后退出,因此调整之后jvm进程会退出。
这种原生方案有点是零依赖、轻量级、开箱即用,缺点是只能打印日志,无法对接现代的可观测系统(如Prometheus),缺少报警机制,只能“看”不能“动”。
3 微服务监控体系(Micrometer/Spring Boot Actuator)
在Spring Boot生态中,工业级的监控标准是基于Micrometer,它相当于指标界(Metrics)的SLF4J。通过Micrometer的ExecutorServiceMetrics,可以将JDK的线程池包装起来,将各项指标无缝暴露给Prometheus,最终在Grafana上画出漂亮的仪表盘。
下面的示例展示注册线程池到全局注册表,只需在Spring Boot的配置类中对线程池进行包装注册:
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.*;
@Configuration
public class ThreadPoolConfig {
@Bean
public ExecutorService orderThreadPool(MeterRegistry meterRegistry) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, 20, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(500),
new ThreadPoolExecutor.AbortPolicy()
);
// 【核心魔法】将线程池注册到 Micrometer,命名为 "order_pool"
return ExecutorServiceMetrics.monitor(meterRegistry, executor, "order_pool");
}
}
程序启动后,访问ip:port/actuator/prometheus端点,可以在页面上看到如下所示的时序数据:
# TYPE executor_active_threads gauge
executor_active_threads{application="userservice",name="clientOutboundChannelExecutor",} 0.0
executor_active_threads{application="userservice",name="brokerChannelExecutor",} 0.0
executor_active_threads{application="userservice",name="messageBrokerTaskScheduler",} 0.0
executor_active_threads{application="userservice",name="clientInboundChannelExecutor",} 0.0
executor_active_threads{application="userservice",name="order_pool",} 0.0
注意,在Spring Boot工程中需要引入如下依赖:
<!-- 1. Spring Boot 官方监控组件 (这会传递引入 micrometer-core) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 2. Micrometer 桥接 Prometheus (负责把底层指标转换为 Prometheus 认识的拉取格式) -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
yaml配置文件中需要加入如下内容:
management:
endpoints:
web:
exposure:
#暴露所有监控端点可以写"*",生产环境为了安全可以只填"health,prometheus,metrics"
include: prometheus
metrics:
tags:
#强烈建议加上应用名标签,方便在 Grafana 中区分是哪个服务
application: userservice
export:
prometheus:
enabled: true #开启Prometheus格式输出
Micrometer方法的优点是可以完美接入云原生Prometheus+Grafana体系,但缺点是依然是静态的,当从大屏上发现队列打满、频繁报警时,采取的措施也只能是重新发版改代码、改配置文件后重启服务。
4 开源动态可监控线程池框架(强烈推荐生产环境使用)
“监控”不仅是为了看,更是为了“治”。
动态线程池的出现一并解决了“看”和“治”的问题,这是业界在实践中提出的一个最佳实践概念,它的核心架构思想是:
- 全面接管:侵入底层接管Spring容器中的所有线程池。
- 配置中心驱动:将corePoolSize、maximumPoolSize、队列容量等参数放到Nacos或Apollo等配置中心。
- 动态感知:监听配置中心的变更事件,通过executor.setCorePoolSize()等底层API实时动态刷新,无需重启服务器。
- 开箱即用的告警:内置企业微信、钉钉、飞书的机器人报警通知机制(队列满了直接发企业微群)。
目前主流的成熟开源方案:
Hippo4j(opengoofy/hippo4j):支持动态变更参数、实时监控、告警,无需改代码,支持多种配置中心,如Nacos、Apollo等。
Dynamic-TP:支持动态线程池、监控告警、适配Dubbo/Tomcat等框架线程池。
4.1 以示例展示Dynamic-TP落地全过程
引入依赖(基于Nacos)
<dependency>
<groupId>org.dromara.dynamictp</groupId>
<artifactId>dynamic-tp-spring-boot-starter-nacos</artifactId>
<version>相应版本</version>
</dependency>
在Nacos中声明配置
把原本写死在Java代码中的参数,全部转移到配置文件中:
spring:
dynamic:
tp:
enabled: true
executors:
- threadPoolName: dtpExecutor-order-pool
threadPoolAliasName: 订单核心线程池
executorType: threadPoolExecutor
corePoolSize: 10
maximumPoolSize: 20
queueType: VariableLinkedBlockingQueue # DTP 提供的支持动态变长队列
queueCapacity: 500
rejectedHandlerType: AbortPolicy
notifyItems: # 告警配置
- type: capacity # 队列容量达到阈值报警
threshold: 80 # 阈值 80%
platforms: [wechat] # 推送到企业微信机器人
- type: reject # 触发拒绝策略报警
threshold: 1
platforms: [wechat]
在代码中一键获取线程池并使用
Java代码变得极其清爽,只需要通过DTP的注册中心根据名称获取即可,如下示例:
import org.dromara.dynamictp.core.DtpRegistry;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.Executor;
@RestController
public class OrderController {
@GetMapping("/submitOrder")
public String submitOrder() {
// 从 DTP 注册表中获取被动态托管的线程池
Executor orderPool = DtpRegistry.getExecutor("dtpExecutor-order-pool");
orderPool.execute(() -> {
System.out.println("处理核心订单逻辑, 当前线程: " + Thread.currentThread().getName());
});
return "提交成功";
}
}
加入订单接口流量突增:
触发告警:企业微信群立刻弹窗告警:“⚠️ 【订单核心线程池】队列容量已达 85%,即将触发拒绝策略!”
动态调参:此时直接登录Nacos控制台,将maximumPoolSize从20改为50,queueCapacity 改为 1000
平滑过渡:保存配置,系统吞吐量翻倍,系统平滑度过流量洪峰。
5 总结
可监控的线程池,本质上是对ThreadPoolExecutor的可观测性做了增强。实际开发中对线程池的使用应当遵循三个层次:
- 代码中导出new Thread()或Executors.newFixedThreadPool()。
- 自定义ThreadPoolExecutor,做物理隔离,并用Micrometer对接Prometheus做大屏展示。
- 全面拥抱Dynamic-TP、Hippo4j等可观测可治理框架,将参数治理权移交至配置中心,现秒级的扩缩容与多维度的报警护航。
我是欧阳方超,把事情做好了自然就有兴趣了,如果你喜欢我的文章,欢迎点赞、转发、评论加关注。我们下次见。
3129

被折叠的 条评论
为什么被折叠?



