You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

并行流统计Human数组高频姓名为何存在数据竞争?

并行统计成人姓名频率的数据竞争问题分析

嘿,我来帮你拆解这段代码里的数据竞争问题——虽然你用了ConcurrentHashMap,但还是踩了并行流里的常见坑!

问题根源:复合操作的非原子性

你在forEach里写的这段计数逻辑是多个操作的组合,并不是原子性的:

nameMap.put(p, nameMap.containsKey(p)? nameMap.get(p)+1 :1)

咱们把这个操作拆成三步看:

  • 第一步:调用nameMap.containsKey(p)检查当前姓名是否在map中
  • 第二步:如果存在,调用nameMap.get(p)获取当前计数,再加1得到新值;如果不存在,新值就是1
  • 第三步:调用nameMap.put(p, 新值)把新计数存回map

虽然ConcurrentHashMap的单个方法(比如containsKey、get、put)都是线程安全的,但这三个步骤组合起来就不是原子操作了。当多个并行线程同时处理同一个姓名时,就会出现「丢失更新」的问题:

举个实际场景:线程A和线程B同时处理姓名"张三",此时map里张三的计数是5。

  1. 线程A执行containsKey和get,拿到计数5,计算出新值6
  2. 线程B在这之后也执行containsKey和get,同样拿到计数5,计算出新值6
  3. 线程A先执行put,把张三的计数改成6
  4. 线程B接着执行put,又把计数改成6
    原本正确的计数应该是7,但现在变成了6,一次计数就这么丢失了!

怎么修复?用原子性的merge方法

ConcurrentHashMap专门提供了原子性的merge方法,可以完美解决这个问题。把forEach里的代码改成这样:

.forEach(p -> nameMap.merge(p, 1, Integer::sum))

merge方法会原子性地完成「检查是否存在→不存在就设为1→存在就把旧值加1」的整个流程,完全避免了数据竞争的问题。

额外小建议

最后找最频繁姓名的那段代码,可以简化一下:不用再开并行流(此时map的规模已经不大了),而且比较器用Comparator.comparingInt可读性更好:

return nameMap.entrySet()
              .stream()
              .max(Comparator.comparingInt(Map.Entry::getValue))
              .map(Map.Entry::getKey)
              .orElse(null); // 可根据业务需求返回默认值

内容的提问来源于stack exchange,提问作者KateS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 07:59:04