ConcurrentHashMap性能测试
之前在测试commons-pool2
相关实现的时候,发现在线程接近500时候,性能瓶颈降低非常厉害,就好像碰到了总体性能的天花板一样,随着线程继续增加而单线程性能急速下降的现象。当时粗略判断其中一个原因是用来存储对象映射关系的java.util.concurrent.ConcurrentHashMap
存在瓶颈导致。
所以今天我特意来测试一下java.util.concurrent.ConcurrentHashMap
的查询性能,其他增改的功能暂时不做测试了。关于另外一个可能的原因java.util.concurrent.atomic.AtomicLong
,我们下期再测。有兴趣的可以先看看我之前对于更强大的多线程计数器java.util.concurrent.atomic.LongAdder
的性能测试:性能测试中的LongAdder。下面是之前遇到两种不同类型的对象池的性能测试文章:通用池化框架GenericObjectPool性能测试、通用池化框架GenericKeyedObjectPool性能测试。
测试方案
先说一下思路和场景设计。思路还是沿用之前的性能测试,通过固定线程的性能模型进行测试,通过调整次数和线程数来测试java.util.concurrent.ConcurrentHashMap
的性能表现。场景设计上我先把java.util.concurrent.ConcurrentHashMap
添加N个key
和value
,然后通过多线程随机从这些key
里面取值。
这样本地测试就有了三个变量线程数
、次数
、key
的数量,本次重点放在了200线程以上的性能表现。
PS:硬件和软件配置参考以前的文章,这里就不多说了。
测试用例
照例方案依旧使用FunTester
性能测试框架提供的能力,采取Groovy
脚本实现。相信有一定Java基础的同学阅读起来是没有问题的。
package com.funtest.groovytest
import com.funtester.base.constaint.FixedThread
import com.funtester.base.constaint.ThreadBase
import com.funtester.frame.SourceCode
import com.funtester.frame.execute.Concurrent
import java.util.concurrent.ConcurrentHashMap
class ConcurrentHashMapTest extends SourceCode {
static ConcurrentHashMap<Integer, Integer> maps = new ConcurrentHashMap<>()
static int times = 1_0000
static int threads = 200
static int num = 100
static def desc = "ConcurrentHashMap性能测试"
public static void main(String[] args) {
1.upto(num) {
maps.put(it, it)
}
ThreadBase.COUNT = false
RUNUP_TIME = 0
new Concurrent(new FunTester(), threads, desc).start()
}
private static class FunTester extends FixedThread {
FunTester() {
super(null, times, true)
}
@Override
protected void doing() throws Exception {
maps.get(getRandomInt(num))
}
@Override
FunTester clone() {
return new FunTester()
}
}
}
测试结果
由于测试中基本都触碰到硬件(CPU)瓶颈,所以本次也就不记录CPU使用率了,相当于都是在CPU资源有限情况下的性能测试数据,其实测试中发现次数影响也不大。
线程数 | 次数(千) | key数量 | 单线程QPS |
---|---|---|---|
200 | 10 | 100 | 3038 |
200 | 20 | 100 | 3539 |
200 | 40 | 100 | 4066 |
200 | 80 | 100 | 4334 |
200 | 10 | 200 | 2823 |
200 | 20 | 200 | 3587 |
200 | 40 | 200 | 4736 |
200 | 10 | 400 | 2919 |
200 | 10 | 50 | 2873 |
200 | 10 | 20 | 3218 |
200 | 10 | 1000 | 3256 |
300 | 10 | 100 | 1893 |
300 | 20 | 100 | 2514 |
300 | 40 | 100 | 3214 |
300 | 20 | 300 | 1798 |
300 | 20 | 500 | 2832 |
500 | 20 | 100 | 1722 |
500 | 20 | 1000 | 1509 |
1000 | 20 | 1000 | 816 |
1000 | 10 | 100 | 724 |
测试到此,结论比较明显了,影响java.util.concurrent.ConcurrentHashMap
的主要因素还是机器CPU资源不够用了。对于相同的资源情况下,线程数更低自然获得更强的单线程性能,如果增加线程确实可以获取更大的总体QPS。在key
值方面,值越多,QPS越低。在测试次数上,自然是字数越多,QPS也大,也符合之前多次测试中的结论。
但是当我重新检查代码的时候却发现一个问题,在com.funtest.groovytest.ConcurrentHashMapTest.FunTester#doing
方法中其实还有一段耗时的请求,就是com.funtester.frame.SourceCode#getRandomInt
,经过我重新测试,发现java.util.concurrent.ConcurrentHashMap
的性能得到了十几倍的提升。
不得不说我大意了,本期文章标题应当修改为java.util.concurrent.ThreadLocalRandom
性能测试。
一下是com.funtester.frame.SourceCode#getRandomInt
的内容:
/**
* 获取随机数,获取1~num 的数字,包含 num
*
* @param num 随机数上限
* @return 随机数
*/
public static int getRandomInt(int num) {
return ThreadLocalRandom.current().nextInt(num) + 1;
}
我依此法重新测试了java.util.concurrent.atomic.AtomicLong
,发现也是QPS超高,排除了我之前的想法。看来commons-pool2
的瓶颈不在这两个地方。以后等我仔细再研究研究,有结论再跟大家分享。
Have Fun ~ Tester !
- 性能测试专题
- Java、Groovy、Go、Python
- FunTester社群风采
- 测试理论鸡汤
- 接口功能测试专题
- FunTester视频专题
- 案例分享:方案、BUG、爬虫
- UI自动化专题
- 测试工具专题
阅读原文,跳转我的仓库地址
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南