BUG 代码:
package com.zl.map.concurrent;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapBug {
public static void main(String[] args) {
Map<String, Integer> map = new ConcurrentHashMap<>(16);
map.computeIfAbsent(
"AaAa",
key -> {
return map.computeIfAbsent(
"BBBB",
key2 -> 42);
}
);
System.out.println("====== end =============");
}
}
执行上面的代码片段会产生死锁。当map中不存在key="AaAa"时,computeIfAbsent会插入该key,并将以下lamda函数的返回值(42)作为它的value。
而这个lamda函数其实会继续去对key="BBBB"的Node进行同样操作,并设置value=42。但是由于这里的“AaAa”和“BBBB”这个字符串的hashCode一样,导致执行出现死锁
问题出在方法: public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
代码上有注释: must not attempt to update any other mappings of this map.
这句话确定了这个问题应该是已知存在的。
所以应该绝对避免在computeIfAbsent中有递归,或者修改map的任何操作。
为了搞清楚原因,我们继续debug concurrentHashMap的源码,发现这种在computeIfAbsent中,
如果尝试修改map的情况下,代码会发生死循环.
由于concurrentHashMap中使用的是cas操作,因此在出现cas嵌套的情况下,就会形成一种『死锁』。举例来说,一个值原来是 1, 我想把它修改成2,正常的cas操作,会比较在修改的那一刻,值是否仍然为1。这种比较,在cas只有一层的情况下,是没有问题的。但是,假如有两层cas,这个值原来是1,第一层把 1 -> 2,在cas还没有生效时,继续进入第二层cas操作,把 2 -> 3,当最终提交时,第二层cas比较当前值是否是2,但由于当前指仍然是1,因此修改无效。最终反复进入循环,形成死锁。
在JDK1.8中使用ConcurrentHashMap时,不要在computeIfAbsent的lambda函数中再去执行更新其它节点value的操作。
在 JDK1.9 之后修复 ?
This is fixed in JDK 9 with JDK-8071667 . When the test case is run in JDK 9-ea, it gives a ConcurrentModification Exception.
java 9
参考:
https://blog.csdn.net/lx1848/article/details/81256443
https://stackoverflow.com/questions/43861945/deadlock-in-concurrenthashmap
https://www.jianshu.com/p/59bd27e137e1