computeIfPresent和computeIfAbsent在连续使用时被视为原子操作吗?

huangapple go评论72阅读模式
英文:

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;
        }

    }
}

换句话说,连续调用computeIfPresentcomputeIfAbsent是否会是原子操作,或者在这种情况下是否仍可能发生竞态条件?

如果我要简化这个问题,考虑以下事件的时间顺序:

  1. 线程A执行第1行(computeIfPresent)为键1。由于键1不存在,线程A不会在键1上调用aggregate函数。
  2. 线程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&lt;String, MyPojo&gt; map = new ConcurrentHashMap&lt;&gt;();

	private void testComputeIfPresentAndAbsent(String key, MyPojo newObj) {
		map.computeIfPresent(key, (k, existingObj) -&gt; aggregate(existingObj, newObj));//Line 1
		map.computeIfAbsent(key, k -&gt; 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 :

  1. 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.
  2. 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&lt;String, MyPojo&gt; map = new ConcurrentHashMap&lt;&gt;();

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&lt;String, MyPojo&gt; map = new ConcurrentHashMap&lt;&gt;();

huangapple
  • 本文由 发表于 2020年5月4日 23:12:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/61595493.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定