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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:14:27