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

ComputeIfAbsent引发Map值重复问题(Short类型场景)

并发场景下ConcurrentHashMap.computeIfAbsent生成重复Short ID的原因及解决方案

问题根源

你遇到的重复ID问题,核心并非computeIfAbsent本身线程不安全,而是valueFactory中的idByName.size()调用不具备原子性,无法保证并发下的唯一性:

  • ConcurrentHashMap.size()的返回值在并发修改时是近似值:它通过遍历所有分段的计数器累加得到,多线程同时添加元素时,不同线程可能读到同一个旧的size值。
  • 针对不同key的computeIfAbsent调用可以并发执行:当两个线程同时往同一个子ConcurrentHashMap中添加不同key时,它们的valueFactory会同时读取当前size。如果此时子map还未完成任何新元素的插入,两个线程都会拿到相同的size(比如0),转成Short后就会生成重复ID。

关于“改用Integer类型解决问题”的误解

你提到改用Integer类型可解决问题,这大概率是测试时的线程调度巧合——Integer类型本身并不能解决并发计数的原子性问题,只是某次测试中刚好没触发并发读取size的场景,本质上依然存在重复ID的风险。

正确解决方案

不要依赖ConcurrentHashMap.size()生成唯一ID,改用AtomicInteger作为每个子map的独立计数器,确保计数操作是原子性的。修改后的代码如下:

Java 8+ 版本(使用Pair)

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang3.tuple.Pair;

public class UniqueIdGenerator {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(4);

        final Map<String, Pair<AtomicInteger, ConcurrentHashMap<String, Short>>> threadSafeMap = new ConcurrentHashMap<>();
        threadSafeMap.put("0", Pair.of(new AtomicInteger(0), new ConcurrentHashMap<>()));
        threadSafeMap.put("1", Pair.of(new AtomicInteger(0), new ConcurrentHashMap<>()));

        for (int i = 1; i <= 10; i++) {
            final int jj = i;
            executorService.submit(() -> {
                int key = jj % 2;
                Pair<AtomicInteger, ConcurrentHashMap<String, Short>> pair = threadSafeMap.get(String.valueOf(key));
                AtomicInteger counter = pair.getKey();
                ConcurrentHashMap<String, Short> idByName = pair.getValue();
                return idByName.computeIfAbsent(String.valueOf(jj), x -> (short) counter.getAndIncrement());
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(5, TimeUnit.SECONDS);
        
        // 输出结果
        threadSafeMap.forEach((k, v) -> System.out.println(k + "=" + v.getValue()));
    }
}

Java 16+ 版本(使用Record更简洁)

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class UniqueIdGenerator {
    // 用Record封装计数器和映射表
    private record CounterMap(AtomicInteger counter, ConcurrentHashMap<String, Short> map) {}

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(4);

        final Map<String, CounterMap> threadSafeMap = new ConcurrentHashMap<>();
        threadSafeMap.put("0", new CounterMap(new AtomicInteger(0), new ConcurrentHashMap<>()));
        threadSafeMap.put("1", new CounterMap(new AtomicInteger(0), new ConcurrentHashMap<>()));

        for (int i = 1; i <= 10; i++) {
            final int jj = i;
            executorService.submit(() -> {
                int key = jj % 2;
                CounterMap counterMap = threadSafeMap.get(String.valueOf(key));
                return counterMap.map().computeIfAbsent(String.valueOf(jj), x -> (short) counterMap.counter().getAndIncrement());
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(5, TimeUnit.SECONDS);
        
        // 输出结果
        threadSafeMap.forEach((k, v) -> System.out.println(k + "=" + v.map()));
    }
}

方案说明

  • AtomicInteger.getAndIncrement()是原子操作,能保证每个线程都拿到唯一的递增数值,彻底避免并发下的重复计数问题。
  • 每个子map对应独立的原子计数器,分组内的ID生成完全隔离,符合你的业务逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 04:44:56