给定以下类,如果多个线程同时执行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;
}
}
}
换句话说,一个接一个地调用computeIfPresent
和computeIfAbsent
是原子操作,还是在这种情况下仍有可能发生竞争条件?
如果我必须简化问题,请考虑以下事件年表:
- 线程 A 执行键 1 的第 1 行 (
computeIfPresent
)。由于键 1 不存在,因此线程 A 不会在键 1 上调用aggregate
函数。
线程 A 执行键 1 - 的第 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<>();