簡體   English   中英

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM