You can get contention when the value already exists.
If you look at the source code for computeIfAbsent(), it's pretty complex but you see that the check whether the value is already present is inside the synchronized block. Consider this alternate version (which doesn't operate atomically):
/**
* Alternate implementation that doesn't block when map already
* contains the value
*/
public V computeIfAbsent2(K key, Function<? super K, ? extends V> mappingFunction) {
V value = get(key);
if (value == null) {
value = mappingFunction.apply(key);
put(key, value);
}
return value;
}
I ran a JMH test comparing this alternate implementation with the original. I ran 20 threads, and used a ConcurrentHashMap containing 20 values that already existed. Each thread would use all 20 values. The test exercised only the case that the value already exists. It ran on OS X. The result (after a 2-minute warmup) was
Benchmark Mode Cnt Score Error Units
ComputIfAbsentTest.benchComputeAbsent thrpt 2 77966.354 ops/ms
ComputIfAbsentTest.benchComputeAbsent2 thrpt 2 463096.033 ops/ms
I also tried running this with Flight Recording enabled, and the contention was clearly visible. Here's an example stack trace:
"local.ComputIfAbsentTest.benchComputeAbsent-jmh-worker-11" #25 daemon prio=5 os_prio=31 tid=0x00007f89da10b000 nid=0x7203 waiting for monitor entry [0x00007000021f8000]
java.lang.Thread.State: BLOCKED (on object monitor)
at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1674)
- waiting to lock <0x0000000795f80540> (a java.util.concurrent.ConcurrentHashMap$Node)
at local.ComputIfAbsentTest.benchComputeAbsent(ComputIfAbsentTest.java:87)
at local.generated.ComputIfAbsentTest_benchComputeAbsent_jmhTest.benchComputeAbsent_thrpt_jmhStub(ComputIfAbsentTest_benchComputeAbsent_jmhTest.java:116)
at local.generated.ComputIfAbsentTest_benchComputeAbsent_jmhTest.benchComputeAbsent_Throughput(ComputIfAbsentTest_benchComputeAbsent_jmhTest.java:76)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:430)
at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:412)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
get
if the key already exists. – Dacoitsynchronize
on the bucket during the computation and update but not during lookups. – Bursitis