ConcurrentHashMap结合AtomicInteger计数异常问题排查
问题分析与解决方案
看起来你遇到的问题是多线程环境下ConcurrentHashMap中Data对象内的计数没有正确累加,主要原因有两个:Data类的设计缺陷导致原子操作失效,以及主线程没有等待异步任务完成就提前输出结果。咱们一步步拆解并修复:
问题根源
1. Data类的setCount方法破坏了原子性
你的setCount方法每次调用都会创建一个新的AtomicInteger实例:
public void setCount(int count) { this.count = new AtomicInteger(count); }
这就导致多线程操作时,不同线程拿到同一个Data对象后,各自基于旧的AtomicInteger值自增,然后替换成新实例——这个过程中会丢失更新。比如:
- 线程A拿到
count=5,自增到6,然后把count替换成新的AtomicInteger(6) - 线程B同时拿到
count=5,自增到6,也替换成新的AtomicInteger(6)
结果相当于只完成了一次自增,而不是两次。
2. 主线程没有等待异步任务执行完毕
你调用ex.shutdown()后直接打印结果,但shutdown()只是停止接收新任务,已经提交的任务可能还在运行。这时候主线程提前输出的是任务未完成时的中间值,自然不符合预期。
修复方案
第一步:重构Data类,保证AtomicInteger实例的唯一性
把count设为final,确保引用不会被修改,直接在同一个原子实例上做自增操作。可以提供便捷方法封装自增逻辑:
import java.util.concurrent.atomic.AtomicInteger; public class Data { private final AtomicInteger count; // final保证引用不可变 // 默认初始计数为0 public Data() { this.count = new AtomicInteger(0); } public AtomicInteger getCount() { return count; } // 封装自增操作,更易用 public int incrementCount() { return count.incrementAndGet(); } }
第二步:修改process方法,直接操作原子实例
去掉不必要的setCount调用,直接对AtomicInteger执行自增:
private static void process() { for(String data: cmap.keySet()) { for(int i=0;i<50;i++) { Data value = cmap.get(data); // 两种方式任选其一: value.getCount().incrementAndGet(); // 或者用封装的方法:value.incrementCount(); } } }
第三步:让主线程等待异步任务完成
在ex.shutdown()后添加等待逻辑,确保所有任务执行完毕再打印结果:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.ConcurrentHashMap; public class TestCmapwithobejct { private static ConcurrentHashMap<String, Data> cmap = new ConcurrentHashMap<>(); private static void process() { for(String data: cmap.keySet()) { for(int i=0;i<50;i++) { Data value = cmap.get(data); value.getCount().incrementAndGet(); } } } public static void main(String[] args) { cmap.put("John", new Data()); cmap.put("Doe", new Data()); ExecutorService ex = Executors.newFixedThreadPool(2); ex.submit(TestCmapwithobejct::process); ex.submit(TestCmapwithobejct::process); ex.shutdown(); // 等待所有任务完成,超时1分钟 try { if (!ex.awaitTermination(1, TimeUnit.MINUTES)) { ex.shutdownNow(); // 超时则强制终止 } } catch (InterruptedException e) { ex.shutdownNow(); Thread.currentThread().interrupt(); } cmap.keySet().forEach(x -> { System.out.println(x + ":" + cmap.get(x).getCount()); }); } }
验证结果
修复后,两个线程各对每个key执行50次自增,最终John和Doe的计数都会是100,完全符合你的预期。
内容的提问来源于stack exchange,提问作者user3310115
相关产品推荐
相关产品推荐

