计算如果一个接一个地使用,是否被视为原子操作?



给定以下类,如果多个线程同时执行testComputeIfPresentAndAbsent方法,代码线程是否安全?

public class ComputeIfPresentAndAbsent {
private ConcurrentHashMap<String, MyPojo> map = new ConcurrentHashMap<>();
private void testComputeIfPresentAndAbsent(String key, MyPojo newObj) {
map.computeIfPresent(key, (k, existingObj) -> aggregate(existingObj, newObj));//Line 1
map.computeIfAbsent(key, k -> newObj);//Line 2
}
private MyPojo aggregate(MyPojo existingObj, MyPojo newPojo) {
newPojo.getField1().add(existingObj.getField1());
newPojo.getField2().add(existingObj.getField2());
return newPojo;
}
class MyPojo {
private BigDecimal field1;
private BigDecimal field2;
public BigDecimal getField1() {
return field1;
}
public void setField1(BigDecimal field1) {
this.field1 = field1;
}
public BigDecimal getField2() {
return field2;
}
public void setField2(BigDecimal field2) {
this.field2 = field2;
}
}
}

换句话说,一个接一个地调用computeIfPresentcomputeIfAbsent是原子操作,还是在这种情况下仍有可能发生竞争条件?

如果我必须简化问题,请考虑以下事件年表:

  1. 线程 A 执行键 1 的第 1 行 (computeIfPresent)。由于键 1 不存在,因此线程 A 不会在键 1 上调用aggregate函数。
  2. 线程 A 执行键 1
  3. 的第 2 行 (computeIfAbsent),并且正在针对键 1 添加对象。同时,线程 B 进入并执行键 1 的第 1 行 (computeIfPresent)。

问题:线程 B 是否会在第 1 行等待,直到线程 A 完成执行第 2 行 (computeIfAbsent),然后才执行aggregate函数?还是线程 B 会立即转到第 2 行而不在第 1 行等待?(假设两个线程在同一个键上运行)

我的理解是,线程 B 不会在第 1 行等待,而线程 A 正在执行第 2 行以获取相同的密钥。这种理解正确吗?如果是,则此代码不是线程安全的,因为多个线程可能会完全错过调用聚合方法。即使我能够通过一些在 10000 个线程中调用testComputeIfPresentAndAbsent方法的示例程序来证明这一理论,我主要感兴趣的是理解为什么这段代码不是线程安全的,以及我的理解是否正确?

是的,你的理解是正确的。computeIfAbsent()/computeIfPresent()ConcurrentMap方法不使用锁定。相反,它们与其他修改同时工作。

另请注意,以下做法既是非线程安全做法,也是不良做法:

private ConcurrentHashMap<String, MyPojo> map = new ConcurrentHashMap<>();

您必须标记该字段final它是否会被其他线程访问。

此外,集合应通过其接口引用:

private final ConcurrentMap<String, MyPojo> map = new ConcurrentHashMap<>();

相关内容

  • 没有找到相关文章

最新更新