ConcurrentHashMap#computeIfAbsent的Javadoc说



但是,据我所知,在remove()中使用clear()mappingFunction方法可以正常工作。例如这个

Key element = elements.computeIfAbsent(key, e -> {
    if (usages.size() == maxSize) {
        elements.remove(oldest);
    }
    return loader.load(key);
});

mappingFunction中使用remove()方法可能带来什么不良后果?

最佳答案

Javadoc清楚地说明了原因:



您不必忘记ConcurrentHashMap旨在提供一种使用线程安全Map的方式,而无需像HashTable这样的旧线程安全Map类那样被锁定。
当对 map 进行修改时,它只会锁定相关的 map ,而不是整个 map 。


computeIfAbsent()是Java 8中添加的新方法。
如果使用不当,也就是说,如果在已经锁定传递给方法的键的映射的computeIfAbsent()主体中,您锁定了另一个键,则输入了可能会破坏ConcurrentHashMap的目的的路径,因为最终您将锁定两个映射。
想象一下,如果您在computeIfAbsent()中锁定了更多映射,并且该方法根本不短,那么问题就来了。 map 上的并发访问将变慢。

因此,computeIfAbsent()的javadoc通过提醒ConcurrentHashMap的原理来强调这个潜在的问题:使其保持简单,快速。

这是说明问题的示例代码。
假设我们有一个ConcurrentHashMap<Integer, String>实例。

我们将开始使用它的两个线程:

  • 第一个线程:thread1,它通过键computeIfAbsent()调用1
  • 第二个线程:thread2,它通过键computeIfAbsent()调用2
  • thread1执行足够快的任务,但没有遵循computeIfAbsent() javadoc的建议:它更新2中的键computeIfAbsent(),这是该方法的当前上下文中使用的另一个映射(即键1)。
    thread2执行足够长的任务。通过遵循javadoc的建议,它使用键computeIfAbsent()调用2:它不会在实现中更新任何其他映射。
    为了模拟较长的任务,我们可以使用Thread.sleep()作为参数的5000方法。
    对于这种特定情况,如果thread2thread1之前开始,则map.put(2, someValue);thread1的调用将被阻止,而thread2computeIfAbsent()不返回,这会锁定键2的映射。

    最后,我们获得了一个ConcurrentHashMap实例,该实例在5秒钟内阻止了键2的映射,而computeIfAbsent()与键1的映射一起被调用。
    这是一种误导,无效,并且违反了ConcurrentHashMap意图和computeIfAbsent()描述,该意图正在为当前键计算值:



    示例代码:
    import java.util.concurrent.ConcurrentHashMap;
    
    public class BlockingCallOfComputeIfAbsentWithConcurrentHashMap {
    
      public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
    
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                map.computeIfAbsent(1, e -> {
                    String valueForKey2 = map.get(2);
                    System.out.println("thread1 : get() returns with value for key 2 = " + valueForKey2);
                    String oldValueForKey2 = map.put(2, "newValue");
                    System.out.println("thread1 : after put() returns, previous value for key 2 = " + oldValueForKey2);
                    return map.get(2);
                });
            }
        };
    
        Thread thread2 = new Thread() {
            @Override
            public void run() {
              map.computeIfAbsent(2, e -> {
                try {
                  Thread.sleep(5000);
                } catch (Exception e1) {
                  e1.printStackTrace();
                }
                String value = "valueSetByThread2";
                System.out.println("thread2 : computeIfAbsent() returns with value for key 2 = " + value);
                return value;
              });
            }
        };
    
        thread2.start();
        Thread.sleep(1000);
        thread1.start();
      }
    }
    

    作为输出,我们总是得到:



    由于对ConcurrentHashMap的读取没有阻塞,因此写入速度很快:



    但是这个 :



    仅在返回computeIfAbsent()的thread2时输出。

    09-13 13:07