如何对并发映射的值进行原子操作?

Posted

技术标签:

【中文标题】如何对并发映射的值进行原子操作?【英文标题】:How to make operations on the value of a concurrent map atomic? 【发布时间】:2022-01-10 03:39:29 【问题描述】:

假设我在一个类中有以下字段:

ConcurrentHashMap<SomeClass, Set<SomeOtherClass>> myMap = new ConcurrentHashMap<SomeClass, Set<SomeOtherClass>>();

这个类的一个实例在许多线程之间共享。

如果我想从与键关联的集合中添加或删除元素,我可以这样做:

Set<SomeOtherClass> setVal = myMap.get(someKeyValue);
setVal.add(new SomeOtherClass());

get 操作是原子的,因此是线程安全的。但是,不能保证在 getadd 指令之间,其他线程不会修改结构,从而干扰第一个线程的执行。

使整个操作原子化的最佳方法是什么?

这是我想出的,但我不认为它非常有效(或者无法充分利用 Java 的结构):

我有一个 ReentrantLock 字段,所以我的班级看起来像这样:

class A 
    ReentrantLock lock = new ReentrantLock();
    ConcurrentHashMap<SomeClass, Set<SomeOtherClass>> myMap = new ConcurrentHashMap<SomeClass, Set<SomeOtherClass>>();

然后方法调用看起来像这样:

lock.lock();
Set<SomeOtherClass> setVal = myMap.get(someKeyValue);
synchronized(setVal) 
    lock.unlock();
    setVal.add(new SomeOtherClass());

我们的想法是,一旦我们确定没有其他人将访问我们试图修改的 Set,我们就会松开锁定。但是,我不认为这是对ConcurrentMap 的最佳利用,或者将锁、并发结构和synchronized 块都用于实现一个操作是很有意义的。

有没有更好的方法来解决这个问题?

【问题讨论】:

从Java 7开始,你不需要在构造函数中重复泛型,你可以简单地将你的hash map构造到new ConcurrentHashMap&lt;&gt;() 【参考方案1】:

ConcurrentHashMap 保证compute(或computeIfAbsentcomputeIfPresent)的整个方法调用是原子完成的。因此,例如,您可以执行以下操作:

myMap.compute(someKeyValue, (k, v) -> v.add(new SomeOtherClass()); return v;);

注意: 使用compute 类似于假设somKeyValue 存在于地图中的原始sn-p。不过,使用computeIfPresent 可能更安全。

【讨论】:

以上是关于如何对并发映射的值进行原子操作?的主要内容,如果未能解决你的问题,请参考以下文章

JMM与并发三大特性

并发编程的三大特性

Java 并发编程线程操作原子性问题 ( 问题业务场景分析 | 使用 synchronized 解决线程原子性问题 )

Java并发编程——原子操作CAS

GO语言并发编程-原子操作

GO语言并发编程-原子操作