ConcurrentHashMap 高并发频次统计需用 merge 或 compute 原子更新、预估初始容量避免扩容、get 读取无需加锁、size() 为估算值、遍历接受弱一致性。

用 ConcurrentHashMap 做高并发频次统计,关键不是“存数字”,而是用对原子方法、避开竞态、控制扩容——否则看似线程安全,实际漏计、重复计或性能骤降。
用 computeIfAbsent 或 merge 替代 if-put
高频场景下,常见错误是先判断再写入:
-
if (!map.containsKey(key)) map.put(key, 1); else map.put(key, map.get(key) + 1);—— 这是两步操作,两个线程可能同时通过 if 判断,结果只加了 1 次,丢一次计数 - 正确做法:用单次原子操作完成更新
推荐写法:
-
map.merge(key, 1, Integer::sum);—— key 存在则 old + 1,不存在则设为 1;底层用 CAS,线程安全且高效 -
map.compute(key, (k, v) -> v == null ? 1 : v + 1);—— 更灵活,适合需自定义逻辑的场景(如限流计数带时间窗口) - 避免用
put(key, get(key) + 1),get 和 put 是两次非原子调用,必然出错
初始化容量要预估,别等运行时扩容
百万级请求下,频繁扩容会触发多线程协作迁移,拖慢吞吐甚至引发短暂卡顿。
立即学习“Java免费学习笔记(深入)”;
- 假设预计统计 150 万个不同 key(比如用户 ID、接口路径),初始容量应设为 ≥ 221 = 2097152(即向上取整到最近的 2 的幂)
- 构造时显式指定:
new ConcurrentHashMap(2097152) - 负载因子保持默认 0.75 即可;调高(如 0.9)会导致链表/红黑树变长,反而降低 get/put 性能
读取频次时直接 get,别加锁
监控大盘、实时告警、TOP N 排行榜等场景,需要频繁查当前计数值。
-
map.get(key)是无锁操作,靠 volatile 字段保证可见性,性能接近 HashMap - 绝对不要包裹 synchronized 或 ReentrantLock —— 会把并发读变成串行,QPS 断崖式下跌
- 注意:
size()返回的是估算值(可能滞后),如需精确总数,建议额外维护一个LongAdder计数器同步累加
遍历统计结果要接受弱一致性
做日志归档、离线分析或生成报表时,常需全量遍历 map。
- ConcurrentHashMap 的
entrySet().iterator()是弱一致的:不抛 ConcurrentModificationException,但可能漏掉刚插入的项,或看到已删除 key 的旧值 - 这对监控、审计类任务完全可接受;若需强一致快照,应在业务层加读锁或改用 CopyOnWriteArrayList 配合定时 dump
- 避免在遍历中调用 remove 或 put —— 虽然不崩溃,但行为不可预测,应先 collect key 再批量操作



















