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

ConcurrentHashMap的get/put操作原子性问题排查与修复

问题:ConcurrentHashMap多线程操作出现计数异常

在多线程环境下对ConcurrentHashMap执行get和put操作时,出现了不符合预期的计数结果。以下是相关代码和输出:

测试代码

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Test {

    private static final List<String> bars = Arrays.asList("1","2","3","4","5","6","7","8","9","10");
    private static final String KEY = UUID.randomUUID().toString();
    private static ExecutorService executorService = null;

    public static void main(String[] args) {
        for (int i = 1; i <= 20; i++) {
            executorService = Executors.newFixedThreadPool(2);
            Map<String, AtomicInteger> map = new ConcurrentHashMap<>();
            performMapOps(map);
            executorService.shutdown();
            try {
                while (!executorService.awaitTermination(1, TimeUnit.SECONDS));
                System.out.println(map.get(KEY));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static void performMapOps(Map<String, AtomicInteger> map) {
        for (int i = 1; i <= bars.size(); i++) {
            executorService.execute(() -> ops(map));
        }
    }

    private static void ops(Map<String, AtomicInteger> map) {
       if (!map.containsKey(KEY)) {
            AtomicInteger atomicInteger = new AtomicInteger(1);
            map.put(KEY, atomicInteger);
        } else {
            map.get(KEY).set(map.get(KEY).intValue() + 1);
        }
    }
}

预期与实际输出

预期每次输出都是10,但实际运行时偶尔会出现9,示例输出:

10
10
10
10
10
10
10
10
10
10
10
10
10
10
10
10
10
9
10
10

异常原因分析

  1. 初始化阶段的竞态条件:containsKey和put是两个独立方法,ConcurrentHashMap仅保证单个方法的线程安全,但组合操作不具备原子性。当多个线程同时判断!map.containsKey(KEY)为真时,都会创建新的AtomicInteger(1)并执行put,后续线程的put会覆盖前面线程放入的实例,导致初始计数被重置,最终总计数少一次。
  2. 计数更新的非原子操作:else分支中map.get(KEY).set(map.get(KEY).intValue() + 1)存在问题:intValue()取值、加1、set赋值这三步并非原子操作,多线程同时执行时可能出现值被覆盖的情况,也会导致计数不准确。

修复方案

利用ConcurrentHashMap的原子性组合方法,结合AtomicInteger的原子自增操作,解决竞态问题:

修改后的ops方法

private static void ops(Map<String, AtomicInteger> map) {
    // 原子性完成:若KEY不存在则创建AtomicInteger(0)放入,否则返回已有值
    AtomicInteger counter = map.computeIfAbsent(KEY, k -> new AtomicInteger(0));
    // 原子性自增1,替代非原子的get+set操作
    counter.incrementAndGet();
}

修复说明

  • computeIfAbsent是原子性方法,保证"检查key是否存在-不存在则创建并放入-返回值"整个流程不会被其他线程打断,彻底消除初始化阶段的竞态。
  • incrementAndGet是AtomicInteger的原子自增方法,直接完成"取值-加1-赋值"的原子操作,避免计数更新时的竞态问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 23:04:54