Java 更新ConcurrentHashMap#ComputeFabSent中其他密钥的后果
来自ConcurrentHashMap#computeIfAbsent的Javadoc说 计算应简短,且不得试图 更新此映射的任何其他映射 但是,据我所见,在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);
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=新线程(){
@凌驾
公开募捐{