Skip to main content

29小节:oneThread与普通线程池性能测试

作者:程序员马丁

在线博客:https://nageoffer.com

note

热门项目实战社群,收获国内众多知名公司面试青睐,近千名同学面试成功!助力你在校招或社招上拿个offer。

oneThread与普通线程池性能测试,元数据信息:

©版权所有 - 拿个offer-开源&项目实战星球专属学习项目,依据《中华人民共和国著作权法实施条例》《知识星球产权保护》,严禁未经本项目原作者明确书面授权擅自分享至 GitHub、Gitee 等任何开放平台。违者将面临法律追究。


内容摘要:我们为了实现动态调整、监控告警这些功能,在原生线程池的基础上增加了不少逻辑,这些额外的开销到底有多大?会不会影响到线程池本身的性能?

课程目录如下所示:

  • 前言
  • 动态线程池增强功能
  • 设计测试方案
  • 性能测试结论

前言

前段时间星球上有位同学在面试中,被问到:今天面试时被问到动态线程池和普通线程池的性能有没有区别?

带着这个疑问,我决定做一次完整的性能测试。这篇文章会记录整个测试过程,包括如何设计测试方案、如何分析性能瓶颈,以及最后得出的结论。

动态线程池增强功能

在开始测试之前,我们需要搞清楚动态线程池相比普通线程池到底多了哪些东西。

1. 监控采集

oneThread 实现了一个 ThreadPoolMonitor,每隔指定时间就会采集一次线程池的运行数据:

private void micrometerMonitor(ThreadPoolRuntimeInfo runtimeInfo) {
// 采集核心指标
Metrics.gauge(metricName("core.size"), tags, registerRuntimeInfo,
ThreadPoolRuntimeInfo::getCorePoolSize);
Metrics.gauge(metricName("active.size"), tags, registerRuntimeInfo,
ThreadPoolRuntimeInfo::getActivePoolSize);
// ... 还有很多其他指标
}

这里有个细节需要注意:getActiveCount()getPoolSize() 这些方法在 JDK 源码里是加锁的。翻了下 ThreadPoolExecutor 的源码:

public int getActiveCount() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock(); // 这里会加锁!
try {
int n = 0;
for (Worker w : workers)
if (w.isLocked())
++n;
return n;
} finally {
mainLock.unlock();
}
}

如果监控采集频率太高,这个锁竞争可能会成为性能瓶颈。

2. 告警检查

ThreadPoolAlarmChecker 会定期检查队列使用率、线程活跃度、拒绝次数:

private void checkAlarm() {
Collection<ThreadPoolExecutorHolder> holders = OneThreadRegistry.getAllHolders();
for (ThreadPoolExecutorHolder holder : holders) {
if (holder.getExecutorProperties().getAlarm().getEnable()) {
checkQueueUsage(holder); // 检查队列
checkActiveRate(holder); // 检查活跃度
checkRejectCount(holder); // 检查拒绝次数
}
}
}

每次检查都要调用那些带锁的 API,这也是潜在的性能开销点。

3. 拒绝策略包装

为了统计拒绝次数,我用 Lambda 包装了原始的拒绝策略:

@Override
public void setRejectedExecutionHandler(RejectedExecutionHandler handler) {
RejectedExecutionHandler handlerWrapper = new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
rejectCount.incrementAndGet(); // 计数
handler.rejectedExecution(r, executor);
}
};
super.setRejectedExecutionHandler(handlerWrapper);
}

虽然只是简单的计数器递增,但每次拒绝都会多执行一次方法调用。

4. 整体架构

用 PlantUML 画个图,看看这些组件是怎么协作的:

从图上可以看出,监控和告警模块都是通过定时任务独立运行的,理论上不应该影响业务线程提交任务的性能。但实际情况如何,还得测了才知道。

动态变更仅涉及到异步更新,且对线程池运行不影响,因此不在整体架构范畴。

设计测试方案

1. 为什么不用 JMH?

一开始我想用 JMH 做基准测试,但很快发现了问题:JMH 是独立运行的,根本没有 Spring 容器,那些监控和告警的定时任务压根就没启动

测出来的结果肯定不准确,因为我们要测的就是监控和告警对性能的影响。

2. 基于 SpringBoot 压测方案

性能测试不能拍脑袋,得有科学的方法。既然动态线程池依赖 Spring 环境,那就直接在 Spring Boot 测试类里模拟压力。

3. 实现测试代码

在进行线程池性能对比测试时,我们设计了一个系统化的流程,以确保结果的准确性和可重复性。

该测试主要针对普通线程池(固定线程配置)和动态线程池(例如 onethreadProducer)在不同负载场景下的表现,包括轻负载、中负载和高负载。

整个过程通过 Spring Boot 的 REST API 端点触发(如 /test/light-load),并输出详细的性能指标对比。下面,我将逐步拆解这个测试流程,理解如何在实际项目中应用类似基准测试。

解锁付费内容,👉 戳