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
方法。对于这种特定情况,如果
thread2
在thread1
之前开始,则map.put(2, someValue);
中thread1
的调用将被阻止,而thread2
的computeIfAbsent()
不返回,这会锁定键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时输出。