概述
是的你没有看错,JDK8版本的ConcurrentHashMap真的有bug,而且不止一个。作为最基础的集合类,我们有必要了解这些bug,让我们使用时能有意识的避开。
话不多说,我们直入主题。ConcurrentHashMap有两个bug,这两个bug都和computeIfAbsent方法有关。
- 如果对相同的key执行多次computeIfAbsent,每次都会加锁。
- 如果对一个key嵌套执行computeIfAbsent,而嵌套执行的key的hash值刚好相同时会导致死循环。
我们都知道computeIfAbsent方法结合了以下两步操作来达到操作的原子性
- 判断当前key是否存在。
- 如果存在直接返回旧值,否则执行插入函数。
为了实现原子性进而保证线程安全,computeIfAbsent是通过加锁来实现的。 但是问题来了,对于相同的key,重复执行多次computeIfAbsent方法,每次都会加锁。如果你的使用场景是多线程高并发场景,这种bug无疑是会严重影响性能的。幸运的是这个bug在JDK9修复了,如果你还在用JDK8,这里提供一个workaround的办法。
public <K,V> V computeIfAbsent(ConcurrentMap<K,V> map, K key, Function<? super K, ? extends V> mappingFunction) {
V value = map.get(key);
if (value == null) {
value = map.computeIfAbsent(key,mappingFunction);
}
return value;
}
接下来我们看看第二个bug,这个bug是个致命的问题。当你对一个key嵌套执行computeIfAbsent,同时嵌套的key刚好hashcode又是相同的话就会触发这个bug。就像下面代码。
public static void main(String[] args) {
Map<String,Integer> map = new ConcurrentHashMap();
map.computeIfAbsent("AaAa",value-> map.computeIfAbsent("BBBB",key->1));
}
让我们仔细分析下computeIfAbsent执行逻辑。
- 第一个computeIfAbsent是没什么问题的,问题出现在第二个computeIfAbsent上,当执行到casTabAt(…) 方法时这个是false,因为已经存在一个ReservationNode了。所以binCount=0。
- 然后因为是ReservationNode,所以hash值是-3,自然不是在扩容,第二步的if返回的是false。
- 第三个if方法返回的是true,但是因为hash值是-3,所以不满足条件,最后又回到了最初的for循环,死循环出现。
这个bug其实很难出现,因为我们日常工作中几乎不会在computeIfAbsent中嵌套执行的情况,这种写法本身就很怪,所以还在使用JDK8的同学建议严禁使用computeIfAbsent嵌套。当然还是强烈建议你升级到JDK9。
最后
以上就是懵懂小馒头为你收集整理的什么?ConcurrentHashMap居然有bug的全部内容,希望文章能够帮你解决什么?ConcurrentHashMap居然有bug所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复