不一样的内容:死磕JDK8中ConcurrentHashMap.computeIfAbsent 死循环 Bug

背景:

   最近有朋友提到了JDK1.8中的ConcurrentHashMap有可能引起CPU飙升的问题,立马恶补,因为运行的生产环境就是1.8版本的,希望没有采坑。

浏览后,发现网上文章千篇一律,不全面。经过一上午的分析、研究,总结如下,共同进步~~~~~~~~~~~~

之前文章中提到过《JDK1.7中HashMap引起CPU100%的问题》,那么JDK8中的ConcurrentHashMap也不一定是安全的。
官方Bug报告: https://bugs.openjdk.java.net/browse/JDK-8062841
JDK9中变化内容: http://hg.openjdk.java.net/jdk9/jdk9/jdk/rev/6dd59c01f011

再次描述下这个bug,运行如下程序

Map<String, Integer> map = new ConcurrentHashMap<>();
map.computeIfAbsent("a", key -> {
    map.put("a", 2);
    return 1;
});

 

JDK1.8中 computeIfAbsent 部分代码

 

 

ReservationNode 在 computeIfAbsent() 方法构建 value 值的时候被用作占位节点。换句话说,computeIfAbsent() 方法会初始化一个 ReservationNode 来占位,它会等待计算完毕后替换当前的占位对象。此时,如果正好赶在 ConcurrentHashMap 达到容量 0.75 的时候进行扩容,由于ConcurrentHashMap 扩容忽略了 ReservationNode 情况。调用put的时候在synchronized (f)没有对ReservationNode处理,所以会出现死循环。因此,可能会导致扩容无法替换占位符,同时占位符等待替换的情况,然后就一直 for 循环处理了。

这也是为什么网上很多人说,如果不存在递归调用computeIfAbsent()是不会发生的。

 

posted @ 2020-06-08 12:41  架构之路  阅读(1410)  评论(0编辑  收藏  举报
========================================================================== 如果您觉得这篇文章对你有帮助,可以【关注我】或者【点赞】,希望我们一起在架构的路上,并肩齐行 ==========================================================================