英文:
Are computeIfPresent and computeIfAbsent when used one after another considered as atomic operations?
问题
给定以下类,如果多个线程同时执行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行(
computeIfPresent
)为键1。由于键1不存在,线程A不会在键1上调用aggregate
函数。 - 线程A执行第2行(
computeIfAbsent
)为键1,正在添加针对键1的对象。与此同时,线程B进来并执行第1行(computeIfPresent
)为键1。
**问题:**线程B是否会在第1行等待,直到线程A完成执行第2行(computeIfAbsent
),然后才执行aggregate
函数?还是线程B会立即继续执行第2行,而不在第1行等待?(假设两个线程在同一个键上操作)
我的理解是,线程B在线程A执行第2行(computeIfAbsent
)时不会在第1行等待。这个理解正确吗?如果是的,那么这段代码就不是线程安全的,因为多个线程可能会完全错过调用aggregate
方法。即使我能够通过在10000个线程中调用testComputeIfPresentAndAbsent
方法的一些示例程序来证明这个理论,我主要是想理解为什么这段代码不是线程安全的,以及我的理解是否正确?
英文:
Given the following class, if multiple threads are executing the testComputeIfPresentAndAbsent
method simultaneously, is the code thread safe? :
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;
}
}
}
In other words, is calling computeIfPresent
and computeIfAbsent
one after another going to be an atomic operation or Is there a possibility of a race condition still occurring in this scenario?
If I had to simplify the question, consider the following chronology of events :
- Thread A executes Line 1 (
computeIfPresent
) for key 1. Since key 1 is not present,aggregate
function is not called on key 1 by Thread A. - Thread A executes Line 2 (
computeIfAbsent
) for key 1 and is in the process of adding the object against key 1. At the same time, Thread B comes in and executes line 1 (computeIfPresent
) for key 1.
Question: Will thread B wait at Line 1 until thread A finishes executing Line 2 (computeIfAbsent
) and only then execute the aggregate
function? Or will Thread B immediately move on to Line 2 without waiting at Line 1? (Assuming both threads operate on the same key)
My understanding is that Thread B will not wait at Line 1 while Thread A is executing Line 2 for the same key. Is this understanding correct? If yes, then this code is not thread safe as multiple threads can miss calling the aggregate method altogether. Even if I was able to prove this theory through some sample program calling the testComputeIfPresentAndAbsent
method in 10000 threads, I am primarily interested in understanding why this code is not thread safe and whether my understanding is correct?
答案1
得分: 1
是的,您的理解是正确的。ConcurrentMap
上的 computeIfAbsent()
/computeIfPresent()
方法不使用锁定。相反,它们与其他修改并发地工作。
另请注意,以下内容既不是线程安全的,也不是良好的实践:
private ConcurrentHashMap<String, MyPojo> map = new ConcurrentHashMap<>();
如果其他线程将访问该字段,您必须将字段标记为 final
。
此外,应该通过它们的接口来引用集合:
private final ConcurrentMap<String, MyPojo> map = new ConcurrentHashMap<>();
英文:
Yes, your understanding is correct. computeIfAbsent()
/computeIfPresent()
methods on ConcurrentMap
do not employ locking. Instead, they work concurrently with other modifications.
Also note that the following is both non-thread-safe and bad practice:
private ConcurrentHashMap<String, MyPojo> map = new ConcurrentHashMap<>();
You have to mark the field final
if it will be accessed by other threads.
Also collections should be referenced by their interface:
private final ConcurrentMap<String, MyPojo> map = new ConcurrentHashMap<>();
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论