来自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()方法有什么不好的后果?
答案 0 :(得分:5)
这是一个不良后果的例子:
{{1}}
此代码导致死锁。
答案 1 :(得分:4)
javadoc清楚地解释了原因:
其他线程在此地图上进行的某些尝试更新操作可能 计算正在进行时被阻止,因此计算应该是 简短,并且不得尝试更新任何其他映射 这张地图。
你不必忘记ConcurrentHashMap
旨在提供一种使用线程安全Map的方法,而不是像HashTable
那样的旧线程安全Map类的情况。 />
当对地图进行修改时,它仅锁定相关的映射而不是整个地图。
ConcurrentHashMap
是一个支持完全并发的哈希表 检索和更新的高预期并发性。
computeIfAbsent()
是Java 8中添加的新方法
如果使用不当,也就是说,如果computeIfAbsent()
的主体已经锁定了传递给方法的密钥的映射,则锁定另一个密钥,您输入的路径可能会导致{{1}的目的失效1}}最后你将锁定两个映射
想象一下,如果你在ConcurrentHashMap
中锁定更多的映射并且该方法根本不短,那么问题就出现了。地图上的并发访问会变慢。
所以computeIfAbsent()
的javadoc通过提醒computeIfAbsent()
的原则来强调这个潜在问题:保持简单快速。
以下是说明问题的示例代码
假设我们有一个ConcurrentHashMap
的实例。
我们将启动两个使用它的线程:
ConcurrentHashMap<Integer, String>
thread1
的第一个主题:computeIfAbsent()
1
thread2
的第二个主题:computeIfAbsent()
2
执行足够快的任务,但它并未遵循thread1
javadoc的建议:它会更新computeIfAbsent()
中的密钥2
,即另一个映射在方法的当前上下文中使用的那个(即密钥computeIfAbsent()
)。
1
执行足够长的任务。它通过遵循javadoc的建议,使用密钥thread2
调用computeIfAbsent()
:它不会更新其实现中的任何其他映射。
为了模拟长任务,我们可以使用2
方法和Thread.sleep()
作为参数。
对于这种特定情况,如果5000
在thread2
之前启动,thread1
中map.put(2, someValue);
的调用将被阻止,而thread1
未返回thread2
}锁定键computeIfAbsent()
的映射。
最后,我们得到一个2
实例,可以在5秒内阻止键ConcurrentHashMap
的映射,同时使用键2
的映射调用computeIfAbsent()
。登记/>
这是误导性的,没有效果,它违背了1
意图和意图计算当前密钥值的ConcurrentHashMap
描述:
如果指定的键尚未与值关联,则尝试 使用给定的映射函数计算其值并输入它 进入此地图,除非null
示例代码:
computeIfAbsent()
作为输出,我们总是得到:
thread1:get()返回值2 = null
的值thread2:computeIfAbsent()返回key 2 =的值 valueSetByThread2
thread1:put()返回后,键2的前一个值= valueSetByThread2
这是快速写的,因为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();
}
}
上的读取没有阻塞:
thread1:get()返回值2 = null
的值
但是这个:
仅当返回thread2thread1:put()返回后,键2的前一个值= valueSetByThread2
ConcurrentHashMap
时才输出。
答案 2 :(得分:2)
这样的建议有点像不走在路中间的建议。你可以做到,你可能不会被汽车击中;如果你看到汽车即将到来,你也可能会离开。
但是,如果你刚刚在人行道(人行道)上停留,那么你会更安全。
如果API文档告诉您不要做某事,那么当然没有什么能阻止您这样做。你可能会尝试这样做,并发现没有不良后果,至少在你测试的有限环境中。您甚至可以深入了解建议的确切原因;您可以仔细检查源代码并证明它在您的用例中是安全的。
但是,API的实现者可以在API文档描述的合同约束内自由更改实现。他们可能会做出改变以阻止您明天的代码工作,因为他们没有义务保留他们明确警告不要使用的行为。
所以,回答你的问题是什么可能是坏的结果:字面上的任何东西(好吧,任何正常完成或抛出RuntimeException
);并且您不一定会在一段时间内或在不同的JVM上观察到相同的后果。
留在人行道上:不要做文件告诉你的事情。