最小化 JDK8 ConcurrentHashMap 检查和设置操作的锁定范围

Sto*_*ika 3 java multithreading concurrenthashmap atomicity java-8

1.

我有多个线程更新 ConcurrentHashMap。每个线程根据键将整数列表附加到映射条目的值。任何线程都没有删除操作。

这里的要点是我想尽可能地最小化锁定和同步的范围。

我看到computeIf...()方法的文档说“在计算过程中,其他线程在这个地图上尝试的一些更新操作可能会被阻止”,这并不令人鼓舞。另一方面,当我查看它的源代码时,我没有观察到它在整个地图上锁定/同步的位置。

因此,我想知道使用 computeIf...() 和以下自产的“方法 2”的理论性能比较

2.

另外,我觉得我在这里描述的问题可能是您可以在 ConcurrentHashMap 上执行最简化的 check-n-set(或通常是“复合”)操作之一

然而,我不是很自信,也找不到很多关于如何在 ConcurrentHashMap 上进行这种简单的复合操作而不锁定/同步整个地图的指南

因此,将不胜感激任何对此的一般性良好实践建议。

public void myConcurrentHashMapTest1() {

    ConcurrentHashMap<String, List<Integer>> myMap = new ConcurrentHashMap<String, List<Integer>>();

    // MAP KEY: a Word found by a thread on a page of a book 
    String myKey = "word1";

    // -- Method 1: 
    // Step 1.1 first, try to use computeIfPresent(). doc says it may lock the
    //      entire myMap. 
    myMap.computeIfPresent(myKey, (key,val) -> val.addAll(getMyVals()));
    // Step 1.2 then use computeIfAbsent(). Again, doc says it may lock the
    //      entire myMap. 
    myMap.computeIfAbsent(myKey, key -> getMyVals());    
}

public void myConcurrentHashMapTest2() {        
    // -- Method 2: home-grown lock splitting (kind of). Will it theoretically 
    //      perform better? 

    // Step 2.1: TRY to directly put an empty list for the key
    //      This may have no effect if the key is already present in the map
    List<Integer> myEmptyList = new ArrayList<Integer>();
    myMap.putIfAbsent(myKey, myEmptyList);

    // Step 2.2: By now, we should have the key present in the map
    //      ASSUMPTION: no thread does removal 
    List<Integer> listInMap = myMap.get(myKey);

    // Step 2.3: Synchronize on that list, append all the values 
    synchronized(listInMap){
        listInMap.addAll(getMyVals());
    }

}

public List<Integer> getMyVals(){
    // MAP VALUE: e.g. Page Indices where word is found (by a thread)
    List<Integer> myValList = new ArrayList<Integer>(); 
    myValList.add(1);
    myValList.add(2);

    return myValList;
}
Run Code Online (Sandbox Code Playgroud)

Dim*_*rov 5

您的假设是基于ConcurrentHashMap对 Javadoc 的误解(按预期使用对您来说太慢了)。Javadoc 没有说明整个地图将被锁定。它也没有说明每个computeIfAbsent()操作都执行悲观锁定。

实际上可以锁定的是一个 bin(又名存储桶),它对应于ConcurrentHashMap. 请注意,这不是包含多个存储桶的 Java 7 映射段。当这样的 bin 被锁定时,可能被阻止的操作只是对散列到同一个 bin 的键的更新。

另一方面,您的解决方案并不意味着避免所有内部锁定ConcurrentHashMap-computeIfAbsent()只是synchronized在更新时可以降级为使用块的方法之一。即使putIfAbsent()您最初为某个键放置一个空列表,如果它没有击中空箱,也可以阻止。

更糟糕的是,您的解决方案并不能保证synchronized批量更新的可见性。这样保证了一个get() 之前发生一个putIfAbsent()它看重它观察,但没有之前发生批量更新和后续之间get()

PS 您可以进一步了解ConcurrentHashMap其 OpenJDK 实现中的锁定:http : //hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/concurrent/ConcurrentHashMap。 java,第 313-352 行。