Java 更新ConcurrentHashMap#ComputeFabSent中其他密钥的后果

Java 更新ConcurrentHashMap#ComputeFabSent中其他密钥的后果,java,java.util.concurrent,concurrenthashmap,Java,Java.util.concurrent,Concurrenthashmap,来自ConcurrentHashMap#computeIfAbsent的Javadoc说 计算应简短,且不得试图 更新此映射的任何其他映射 但是,据我所见,在mappingFunction中使用remove()和clear()方法可以很好地工作。比如这个 Key element = elements.computeIfAbsent(key, e -> { if (usages.size() == maxSize) { elements.remove(oldest);

来自ConcurrentHashMap#computeIfAbsent的Javadoc说

计算应简短,且不得试图 更新此映射的任何其他映射

但是,据我所见,在
mappingFunction
中使用
remove()
clear()
方法可以很好地工作。比如这个

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

mappingFunction
中使用remove()方法会有什么不良后果?

下面是一个不良后果的示例:

ConcurrentHashMap<Integer,String> cmap = new ConcurrentHashMap<> ();
cmap.computeIfAbsent (1, e-> {cmap.remove (1); return "x";});
ConcurrentHashMap cmap=new ConcurrentHashMap();
cmap.computeIfAbsent(1,e->{cmap.remove(1);返回“x”;});

这段代码会导致死锁。

这样的建议有点像不要走在路中间的建议。你可以做到,而且你很可能不会被车撞到;如果你看到车来了,你也可以让开

然而,如果你一开始就呆在人行道上,你会更安全

如果API文档告诉您不要做某事,那么当然没有什么可以阻止您这样做。你可能会尝试这样做,并发现没有不良后果,至少在你测试的有限环境中是这样。你甚至可以深入挖掘,找出这些建议存在的确切原因;您可以仔细检查源代码并证明它在您的用例中是安全的

但是,API的实现者可以在API文档描述的合同约束范围内自由更改实现。他们可能会做出明天停止您的代码工作的更改,因为他们没有义务保留明确警告不要使用的行为

因此,要回答您的问题,坏的后果可能是什么:字面上的任何东西(好的,任何正常完成或抛出
运行时异常的东西)
;随着时间的推移,或者在不同的JVM上,您不一定会看到相同的结果


保持冷静:不要做文档中告诉你不要做的事。

javadoc清楚地解释了原因:

其他线程在此映射上尝试的某些更新操作可能是 在计算过程中被阻止,因此计算应为 简短且简单,并且不得尝试更新 这张地图

不要忘记,
ConcurrentHashMap
旨在提供一种使用线程安全映射的方法,而不会像
HashTable
那样锁定较旧的线程安全映射类
当对映射进行修改时,它只锁定相关映射,而不锁定整个映射。

ConcurrentHashMap
是一个哈希表,支持 更新的检索和高预期并发性

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

因此,
computeIfAbsent()
的javadoc强调了这个潜在的问题,它提醒了
ConcurrentHashMap
的原则:保持简单和快速


下面是说明该问题的示例代码。
假设我们有一个
ConcurrentHashMap
的实例

我们将启动两个使用它的线程:

  • 第一个线程:
    thread1
    ,它使用键
    1
  • 第二个线程:
    thread2
    ,它使用键
    2
thread1
执行一个足够快的任务,但它没有遵循
computeifassent()
javadoc的建议:它更新
computeifassent()
中的键
2
,这是在方法的当前上下文中使用的另一个映射(即键
1
)。

thread2
执行足够长的任务。它使用键
2
调用
ComputeFabSent()
,方法是遵循javadoc的建议:它不会在实现中更新任何其他映射。
为了模拟长任务,我们可以使用
Thread.sleep()
方法,并将
5000
作为参数。

对于这种特定情况,如果
thread2
thread1
之前开始,则调用
map.put(2,someValue)thread1
中的code>将被阻止,而
thread2
不会返回锁定密钥映射的
computeIfAbsent()

最后,我们得到一个
ConcurrentHashMap
实例,该实例在5秒内阻止键
2
的映射,而
ComputeFabSent()
则通过键
1
的映射被调用
它具有误导性,无效,并且违背了
ConcurrentHashMap
意图和
ComputeFabSent()
描述,意图是计算当前密钥的值:

如果指定的键尚未与值关联,则尝试 使用给定的映射函数计算其值并输入 除非为null,否则将导入此映射

示例代码:

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();
  }
}
import java.util.concurrent.ConcurrentHashMap;
使用ConcurrentHashMap阻止ComputerIfAbcentWithConcurrentHashMap调用的公共类{
公共静态void main(字符串[]args)引发InterruptedException{
ConcurrentHashMap=新的ConcurrentHashMap();
线程thread1=新线程(){
@凌驾
公开募捐{