Vit*_*nko 8 java concurrenthashmap java.util.concurrent
Javadoc ConcurrentHashMap#computeIfAbsent说
计算应该简短,并且不得尝试更新此映射的任何其他映射.
但是,从我看到的,使用remove()和clear()内部方法mappingFunction工作正常.例如这个
Key element = elements.computeIfAbsent(key, e -> {
if (usages.size() == maxSize) {
elements.remove(oldest);
}
return loader.load(key);
});
Run Code Online (Sandbox Code Playgroud)
在内部使用remove()方法有什么不好的后果mappingFunction?
这是一个不好后果的例子:
ConcurrentHashMap<Integer,String> cmap = new ConcurrentHashMap<> ();
cmap.computeIfAbsent (1, e-> {cmap.remove (1); return "x";});
Run Code Online (Sandbox Code Playgroud)
此代码导致死锁.
javadoc清楚地解释了原因:
当计算正在进行时,其他线程对此映射的某些尝试更新操作可能会被阻止,因此计算应该简短且简单,并且不得尝试更新此映射的任何其他映射。
您不要忘记,它的ConcurrentHashMap设计目的是提供一种使用线程安全 Map 的方法,而无需像旧线程安全 Map 类那样锁定HashTable。
当映射发生修改时,它仅锁定相关映射而不是整个映射。
ConcurrentHashMap是一个哈希表,支持检索的完全并发性和更新的高预期并发性。
computeIfAbsent()是Java 8中添加的一个新方法。
如果使用不当,也就是说,如果在该方法的主体中computeIfAbsent()已经锁定了传递给该方法的键的映射,那么您锁定了另一个键,那么您将进入一条可能无法达到目的的路径最后ConcurrentHashMap你将锁定两个映射。想象一下,如果您在内部锁定更多映射并且该方法一点也不短,会
出现什么问题。computeIfAbsent()地图上的并发访问会变慢。
因此,javadoccomputeIfAbsent()通过提醒以下原则来强调这个潜在问题ConcurrentHashMap:保持简单和快速。
这是说明该问题的示例代码。
假设我们有一个ConcurrentHashMap<Integer, String>.
我们将启动两个使用它的线程:
thread1调用computeIfAbsent()1thread2调用computeIfAbsent()2thread1执行足够快的任务,但它不遵循 javadoc 的建议:它更新中的computeIfAbsent()key ,这是方法当前上下文中使用的另一个映射(即 key )。
执行足够长的任务。它按照 javadoc 的建议使用密钥进行调用:它不会更新其实现中的任何其他映射。
为了模拟长任务,我们可以使用带有参数的方法。
对于这种特定情况,如果在before 之前启动,则in的调用将被阻止,而不会返回锁定键的映射。2computeIfAbsent()1
thread2computeIfAbsent()2Thread.sleep()5000thread2thread1map.put(2, someValue);thread1thread2computeIfAbsent()2
最后,我们得到一个ConcurrentHashMap实例,该实例在使用 key 的映射调用2时会在 5 秒内阻止computeIfAbsent()key 的映射1。
它具有误导性,无效,并且违背了ConcurrentHashMap意图和computeIfAbsent()意图计算当前键的值的描述:
如果指定的键尚未与值关联,则尝试使用给定的映射函数计算其值并将其输入到此映射中,除非为 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();
}
}
Run Code Online (Sandbox Code Playgroud)
作为输出,我们总是得到:
thread1 : get() 返回键 2 = null 的值
thread2 :computeIfAbsent() 返回键 2 的值 = valueSetByThread2
thread1 : put() 返回后,键 2 的先前值 = valueSetByThread2
写入速度很快,因为读取ConcurrentHashMap不会阻塞:
thread1 : get() 返回键 2 = null 的值
但是这个 :
thread1 : put() 返回后,键 2 的先前值 = valueSetByThread2
仅当 thread2 返回时输出computeIfAbsent()。
| 归档时间: |
|
| 查看次数: |
549 次 |
| 最近记录: |