JMH中@Threads(1)仍存在2线程,字典取值为空的原因?
首先要明确:你看到的Thread.activeCount()返回2是正常现象,和你遇到的字典取值失败没有直接关系。
为什么@Threads(1)还会有2个活跃线程?
@Threads(1)指定的是执行基准测试逻辑的工作线程数为1,但JMH框架本身会启动一个额外的协调线程——它负责控制热身、测量阶段的切换,统计测试数据,管理整个基准测试的生命周期。这个线程和你的工作线程属于同一个线程组,所以Thread.activeCount()会把它算进去,显示为2个线程。这是JMH的正常设计,不需要担心这个数字。
多线程下字典取值失败的核心原因
你遇到的"无法通过key获取对应值"问题,大概率是字典(dict)的线程安全性不足导致的,常见场景包括:
使用非线程安全的集合实现
如果你的dict是普通的HashMap(或者其他非线程安全的Map实现),在多线程并发读写的场景下,会出现内部数据结构损坏的情况——比如扩容时的链表环、哈希冲突导致的key丢失,最终表现为明明存在的key却返回null,导致你的列表l变成空列表。可见性问题
如果dict的初始化或更新操作没有正确同步,某个线程写入的key/值可能对其他线程不可见。比如在没有同步机制的情况下,线程A刚把key存入dict,线程B读取时可能看不到这个新的key,导致取值失败。初始化时机错误
虽然你用了@State(Scope.Benchmark)(意味着整个基准测试共享一个BState实例),但如果dict的初始化逻辑没有在所有工作线程启动前完成,或者初始化过程本身存在并发问题,也可能导致部分线程看到的dict是不完整的。
解决建议
针对这些问题,你可以按以下步骤排查和修复:
- 替换为线程安全的集合:把
HashMap换成ConcurrentHashMap,它专门针对并发场景做了优化,能安全支持多线程下的读写操作,不需要额外加锁。 - 确保初始化的原子性:如果
dict是在@Setup(Level.Trial)中初始化的,这个方法会在所有工作线程启动前执行一次,是线程安全的——但要确保初始化过程中没有并发修改dict的逻辑。 - 验证多线程下的dict状态:可以在基准测试方法中打印当前线程ID和
dict.keySet()的内容,看看是不是某个线程看到的dict确实缺少目标key,以此定位是可见性问题还是集合损坏。 - 避免在基准测试中使用
System.out.println:虽然你用来调试,但System.out是同步操作,会严重影响基准测试的性能结果,建议换成日志框架(如SLF4J)或者在测试完成后打印结果。
内容的提问来源于stack exchange,提问作者cng

