关于computeIfAbsent代码改写正确性及原子性的技术问询
关于computeIfAbsent改写及相关问题的解答
一、你的代码改写不正确
原代码的完整逻辑是:
- 以
info.getOwner()作为键,检查map中是否存在对应值 - 如果不存在,创建空的
CopyOnWriteArrayList并放入map - 无论键是否存在,都将
info添加到返回的列表中
而你的改写代码只处理了“键不存在”的情况,直接创建包含info的列表放入map;但如果键已经存在,完全没有执行添加info的操作,这和原代码逻辑完全不符。
正确的非Lambda等价写法应该是:
Object owner = info.getOwner(); CopyOnWriteArrayList<Info> list = map.get(owner); if (list == null) { list = new CopyOnWriteArrayList<>(); map.put(owner, list); } list.add(info);
二、不用Lambda会损失这些优势
- 并发安全的原子性:
computeIfAbsent是原子操作,像ConcurrentHashMap这类并发Map实现会保证“检查键是否存在→生成新值→插入map”的整个流程不会被其他线程打断。而自己手写的get+put在并发场景下会出现竞态条件——比如两个线程同时发现键不存在,都会创建列表并执行put,最终其中一个的结果会被覆盖。 - 代码简洁性:Lambda写法把“不存在则创建+后续操作”的逻辑浓缩成一行,意图直接明了;拆分后的代码需要多行才能完成相同逻辑,可读性和简洁性都打折扣。
- 延迟对象创建:Lambda里的
new CopyOnWriteArrayList<>()只有在键确实不存在时才会执行,避免了不必要的对象实例化(虽然这个优化影响不大,但属于设计上的合理考量)。
三、原子插入的相关疑问
首先明确:computeIfAbsent的原子性只覆盖“检查键、生成值、插入map”这一步,返回列表后的add()是单独的操作。不过你用的CopyOnWriteArrayList本身的add()方法是线程安全的,所以这一步不存在并发问题。
如果换成普通的ArrayList,add()不是线程安全的,那确实需要额外同步,但computeIfAbsent依然能保证列表的初始化是原子的。
至于用putIfAbsent是否更合适?其实反而更麻烦,要实现和原代码一样的逻辑,你需要处理putIfAbsent的返回值:
Object owner = info.getOwner(); CopyOnWriteArrayList<Info> newList = new CopyOnWriteArrayList<>(); CopyOnWriteArrayList<Info> existingList = map.putIfAbsent(owner, newList); CopyOnWriteArrayList<Info> targetList = existingList == null ? newList : existingList; targetList.add(info);
这种写法比computeIfAbsent啰嗦得多,而computeIfAbsent正是为了简化这种“不存在则创建,然后操作目标对象”的场景而生的,所以原代码的写法才是更合适的选择。
内容的提问来源于stack exchange,提问作者Zhro
相关产品推荐
相关产品推荐

