![](/img/trans.png)
[英]Why ConcurrentHashMap::putIfAbsent is faster than ConcurrentHashMap::computeIfAbsent?
[英]ConcurrentHashMap computeIfAbsent
現在沒有在java 8的Javadoc推出了一個新的API computeIfAbsent 它的ConcurrentHashMap的impelementation狀態:
如果指定的鍵尚未與值關聯,則嘗試使用給定的映射函數計算其值,並將其輸入此映射,除非為null。 整個方法調用是以原子方式執行的,因此每個鍵最多應用一次該函數。 其他線程在此映射上的某些嘗試更新操作可能在計算進行時被阻止,因此計算應該簡短,並且不得嘗試更新此映射的任何其他映射。
那么,在密鑰已經存在且計算不需要的情況下,它對鎖定此實現有什么看法呢? 即使不需要計算,只是映射函數調用是同步的,以防止調用函數兩次,整個方法computeIfAbsent是否如文檔中所述同步?
ConcurrentHashMap的實現非常復雜,因為它專門設計為允許並發可讀性,同時最小化更新爭用。 在非常高的抽象級別,它被組織為一個分段的哈希表。 所有讀取操作都不需要鎖定,並且(引用javadoc) “沒有任何支持以阻止所有訪問的方式鎖定整個表” 。 為了實現這一點,內部設計非常復雜(但仍然很優雅),在節點中保存鍵值映射,可以以各種方式(例如列表或平衡樹)進行排列,以便利用細粒度鎖。 如果您對實現細節感興趣,還可以查看源代碼 。
試着回答你的問題:
那么,在密鑰已經存在且計算不需要的情況下,它對鎖定此實現有什么看法呢?
可以合理地認為,與任何讀取操作一樣,不需要鎖定來檢查密鑰是否已經存在並且不需要執行映射功能。
整個方法computeIfAbsent是否如文檔中所述同步,即使不需要計算,或只是映射函數調用是否同步以防止調用函數兩次?
不,該方法在鎖定方面不同步 ,但從調用者的角度來看,它是以原子方式執行的(即,映射函數最多應用一次)。 如果未找到密鑰,則必須使用映射函數計算的值執行更新操作,並且在調用該函數時涉及某種鎖定。 可以合理地認為這種鎖定是非常細粒度的,並且只涉及表的一小部分(以及必須存儲密鑰的特定數據結構),這就是為什么(引用javadoc,強調我的) “在計算進行過程中, 某些其他線程嘗試的更新操作可能會被阻止” 。
當值已經存在,你可以得到的爭奪。
如果查看computeIfAbsent()的源代碼,它會非常復雜,但是您會看到檢查該值是否已經存在於synchronized塊內。 考慮這個備用版本(不能以原子方式運行):
/**
* 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;
}
我運行了一個JMH測試,將這個替代實現與原始實現進行比較。 我運行了20個線程,並使用了包含20個已存在的值的ConcurrentHashMap。 每個線程將使用所有20個值。 測試僅執行值已存在的情況。 它在OS X上運行。結果(經過2分鍾的熱身后)是
Benchmark Mode Cnt Score Error Units
ComputIfAbsentTest.benchComputeAbsent thrpt 2 77966.354 ops/ms
ComputIfAbsentTest.benchComputeAbsent2 thrpt 2 463096.033 ops/ms
我也嘗試在啟用Flight Recording的情況下運行此功能,並且爭用清晰可見。 這是一個示例堆棧跟蹤:
"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)
錯誤修復@RolandIllig提到,如果密鑰不是bin中的第一個密鑰,則仍然可能發生爭用。 我使用JMH和Java 10測試了這個。
luckyKey的吞吐量:
Result: 324172.798 ±(99.9%) 15244.448 ops/ms [Average]
unluckyKey的吞吐量:
Result: 15386.202 ±(99.9%) 526.877 ops/ms [Average]
基准代碼
@Threads(8)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class ComputeIfAbsentBenchmark {
@State(Scope.Benchmark)
public static class MyState {
private final Map<String, Integer> map = new ConcurrentHashMap<>();
public MyState() {
for (int i = 0; i < 100; i++)
map.put(Integer.toString(i), i);
}
}
@Benchmark
public void luckyKey(final MyState state) {
state.map.computeIfAbsent("1", key -> 100);
}
@Benchmark
public void unluckyKey(final MyState state) {
state.map.computeIfAbsent("98", key -> 100);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.