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

Java中ConcurrentHashMap创建唯一Item的线程安全问题及优化

多线程环境下唯一名称Item创建的线程安全问题修复

问题场景

需要创建名称唯一的Item并缓存后返回,运行在多线程环境中,现有代码存在线程安全问题,具体问题标注在代码注释中:

class ItemOperations {

    private ConcurrentMap<String, Item> store = new ConcurrentHashMap<>();

    Item createItem(String name) throws Exception {

        // 我认为该操作因ConcurrentHashMap而具备线程安全性
        if (store.containsKey(name)) {
            throw new Exception("Already Exists");
        }

        // Item创建是耗时操作
        Item newItem = new Item();
        // *问题1*:上述代码中,多个线程会创建多个不同的对象,对吗?


        // putIfAbsent因ConcurrentMap具备线程安全性..
        // 失败的线程不会更新值..(若有误请指正)
        store.putIfAbsent("newItemName", newItem);


        // *问题2*:不同线程会返回不同的对象,第二个线程返回的是一个临时创建但未存入store的新对象,这会返回错误数据
        return newItem;
    }
}

问题解答

关于问题1

是的,多个线程会创建多个不同的Item对象。因为containsKey判断和Item对象创建这两步不是原子操作:当线程A执行完containsKey确认名称不存在后,还没执行putIfAbsent时,线程B也可能完成containsKey判断并得到同样的“不存在”结果,进而创建新的Item对象。

关于问题2

没错,后执行putIfAbsent的线程会操作失败(已有线程完成了对象存入),但它返回的是自己创建的未被缓存的Item对象,导致调用方拿到的对象与缓存中的不一致,属于错误数据。

修复方案

不需要用synchronized包裹整个方法(那样会完全丧失ConcurrentHashMap的并发优势,大幅降低性能),可以利用ConcurrentMap的computeIfAbsent方法,它能保证检查存在性+创建对象+存入缓存整个逻辑的原子性:

class ItemOperations {

    private ConcurrentMap<String, Item> store = new ConcurrentHashMap<>();

    Item createItem(String name) throws Exception {
        // 先快速检查,利用ConcurrentHashMap get方法的线程安全性
        Item existing = store.get(name);
        if (existing != null) {
            throw new Exception("Already Exists");
        }
        
        // 原子性创建并存入:仅当name不存在时才执行lambda创建对象
        Item newItem = store.computeIfAbsent(name, key -> new Item());
        
        // 二次校验:避免get之后、computeIfAbsent之前,其他线程已创建该名称的Item
        if (newItem != store.get(name)) {
            throw new Exception("Already Exists");
        }
        
        return newItem;
    }
}

对原有思考的分析

  • 用synchronized包裹整个方法确实能解决问题,但会把并发操作强制转为串行,完全浪费了ConcurrentHashMap的并发性能,不推荐。
  • 用put替代putIfAbsent更不可取:put会覆盖已有的值,不仅产生无用写入,还会导致先存入的正确Item被后续线程的对象覆盖,直接破坏数据唯一性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 04:22:42