ConcurrentHashMap竟然也有死循环
前几天和拼多多及政采云的架构师们闲聊,其中拼多多架构师说遇到了一个ConcurrentHashMap死循环问题,当时心里想这不科学呀?ConcurrentHashMap怎么还有死循环呢,毕竟它已经解决HashMap中rehash中死循环问题了,但是随着深入的分析,发现事情并没有之前想的那么简单~ (以下分析基于jdk版本:jdk1.8.0_171)
保险起见,不能直接贴出出现问题的业务代码,因此将该问题简化成如下代码:
1 | ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>(); |
感兴趣的小伙伴可以在电脑上运行下,话不说多,先说下问题原因:当执行computeIfAbsent
时,如果key对应的slot为空,此时会创建ReservationNode
对象(hash值为RESERVED=-3
)放到当前slot位置,然后调用mappingFunction.apply(key)
生成value,根据value创建Node之后赋值到slow位置,此时完成computeIfAbsent
流程。但是上述代码mappingFunction
中又对该map进行了一次put操作,并且触发了rehash操作,在transfer
中遍历slot数组时,依次判断slot对应Node是否为null、hash值是否为MOVED=-1、hash值否大于0(list结构)、Node类型是否是TreeBin(红黑树结构),唯独没有判断hash值为RESERVED=-3
的情况,因此导致了死循环问题。
问题分析到这里,原因已经很清楚了,当时我们认为,这可能是jdk的“bug”
,因此我们最后给出的解决方案是:
- 如果在rehash时出现了
slot
节点类型是ReservationNode
,可以给个提示,比如抛异常; - 理论上来说,
mappingFunction
中不应该再对当前map进行更新操作了,但是jdk并没有禁止不能这样用,最好说明下。
最后,另一个朋友看了computeIfAbsent
的注释:
1 | /** |
我们发现,其实人家已经知道了这个问题,还特意注释说明了。。。我们还是too yong too simple
啊。至此,ConcurrentHashMap死循环问题告一段落,还是要遵循编码规范,不要在mappingFunction中再对当前map进行更新操作。其实ConcurrentHashMap死循环不仅仅出现在上述讨论的场景中,以下场景也会触发,原因和上述讨论的是一样的,代码如下,感兴趣的小伙伴也可以本地跑下:
1 | ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>(); |
最后,一起跟着computeIfAbsent
源码来分下上述死循环代码的执行流程,限于篇幅,只分析下主要流程代码:
1 | public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { |
好文推荐:
- 本文链接:http://luoxn28.github.io/2019/06/01/concurrenthashmap-jing-ran-ye-you-si-xun-huan/
- 版权声明:本文章著作权归作者所有,任何形式的转载都请注明出处。
分享