文档章节

JDK8的ConcurrentHashMap也会造成CPU 100%

o
 osc_n6euf5h6
发布于 2019/03/20 00:44
字数 1082
阅读 75
收藏 0

精选30+云产品,助力企业轻松上云!>>>

转载:不止 JDK7 的 HashMap ,JDK8 的 ConcurrentHashMap 也会造成 CPU 100%?原因与解决~

现象

大家可能都听过JDK7中的HashMap在多线程环境下可能造成CPU 100%的现象,这个由于在扩容的时候put时产生了死链,由此会在get时造成了CPU 100%。这个问题在JDK8中的HashMap获得了解决。其实JDK7中的HashMap在多线程环境下不止只有CPU 100%这一共怪异现象,它还可能造成插入的数据丢失,有兴趣的读者可以自行了解下。

对于HashMap多线程的问题,我们通常会这么反问:HashMap设计上就不是多线程安全的,何必要去在多线程环境下用呢?的确如此,我们不会傻到显式的在多线程环境下调用,但是又可能在你所关注的视角范围外是多线程的,其隐式地让HashMap置于多线程环境下了,这个又难以一下子察觉到。再者,对于HashMap多线程的问题,我们很多时候推荐使用ConcurrentHashMap来代替HashMap应用于多线程的环境,很不巧的是ConcurrentHashMap也有可能会造成CPU 100%的异常现象。这个怪异现象存在于JDK8的ConcurrentHashMap中,在JDK9中已经得到修复,可以参见:https://bugs.openjdk.java.net/browse/JDK-8062841

什么情况下JDK8的ConcurrentHashMap会出现这个Bug呢?首先我们来运行一下这段代码:

Map<String, String> map = new ConcurrentHashMap<>();
map.computeIfAbsent("AaAa",
        key -> map.
        computeIfAbsent("BBBB", key2 -> "value"));

你会惊奇的发现这个程序一直处于Running状态,我们通过top -Hp [pid]命令查看到其中一个线程的CPU使用率接近100%,参考下图:

可以看到pid为31417的东东,我们再通过jstack -l [pid]命令查看到对应的线程为:

注意将nid=0x7ab9的16进制转为10进制就是31417。可以看到问题是发生在了computeIfAbsent方法中,我们将示例中的程序换成下面这段程序也会同样出现CPU 100%的Bug:

map.computeIfAbsent("AaAa",
        (String key) -> {
            map.put("BBBB", "value");
            return "value";
        });

问题的关键在于递归使用了computeIfAbsent方法,笔者在stackoverflow上还搜索到了同类型的问题,下面的示例程序中调用fibonacci方法同样也会造成CPU 100%.

static Map<Integer, Integer> concurrentMap = new ConcurrentHashMap<>();

public static void main(String[] args) {
    System.out.println("Fibonacci result for 20 is" + fibonacci(20));
}

static int fibonacci(int i) {
    if (i == 0)
        return i;

    if (i == 1)
        return 1;

    return concurrentMap.computeIfAbsent(i, (key) -> {
        System.out.println("Value is " + key);
        return fibonacci(i - 2) + fibonacci(i - 1);
    });
}

至于为什么会发生这个BUG,答案就在ConcurrentHashMap中的computeIfAbsent方法中。

原因

map.computeIfAbsent(key1, mappingFunction)

如果当前key1-hash对应的tab位(可以理解为槽)刚好是空的,在计算mappingFunction之前会

  • step1: 先往对应位置放一个ReservationNode占位

  • step2: 然后计算mappingFunction的值value,

  • step3: 再将value组装成最终NODE, 把占位的ReservationNode换成最终NODE;

 

这时如果:
mappingFunction 中用到了 当前map的computeIfAbsent方法, 很不巧 key2-hash的槽为和key1的是同一个,
因为key1已经在槽中放入了占位节点, 在处理key2时候for循环的所以处理条件都不符合 程序进入了死循环

但是如果:

key2-hash的槽位和key1的不一样, 是不会发生死循环

 

多线程问题:

因为ConcurrentHashMap在处理上述step1-step3是同步的, 而且在处理时候会同步获取的值, 所以是不存在线程不安全的, 纯粹是当前线程死循环

Thread1 通过cas 在槽x放了个ReservationNode(RN1), 然后假设mappingFunction执行的很慢

Thread2 在槽x和Thread竞争, cas失败没有抢到占位符; 进行下一轮for循环, 这是因为槽x中已经被放置了RN1, 所以Thread2获取到这个RN1,在执行synchronized(RN1) 时候被thread1block住。

code sample:

public static void main(String[] args) throws IOException {
    ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
    //caseA: dead loop
//        map.computeIfAbsent("AA", key -> map.computeIfAbsent("BB", k->"bb"));

    //caseB: block, but no dead loop
    new Thread(()->map.computeIfAbsent("AA", key -> waitAndGet())).start();

    new Thread(()->{
        try {
            TimeUnit.SECONDS.sleep(3);  //delay 1 second
        } catch (InterruptedException e) {}
        map.computeIfAbsent("BB", key-> "bb");
    }).start();

}

private static String waitAndGet(){
   try {
       TimeUnit.SECONDS.sleep(20);
   } catch (InterruptedException e) {
   }
   return "AAA";
}

解决

怎么规避这个问题呢?只要不在递归中使用computeIfAbsent方法就好啦,或者降级用可爱的分段锁,或者升级JDK9~。

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
不止JDK7的HashMap,JDK8的ConcurrentHashMap也会造成CPU 100%

版权声明:本文为博主原创文章,未经博主朱小厮允许不得转载。 https://blog.csdn.net/u013256816/article/details/84917112 大家可能都听过JDK7中的HashMap在多线程环境下可能造成CPU 100%的...

朱小厮
2018/12/09
0
0
不止JDK7的HashMap,JDK8的ConcurrentHashMap也会造成CPU 100%

大家可能都听过JDK7中的HashMap在多线程环境下可能造成CPU 100%的现象,这个由于在扩容的时候put时产生了死链,由此会在get时造成了CPU 100%。这个问题在JDK8中的HashMap获得了解决。其实JDK...

编程SHA
2019/04/08
16
0
为什么HashMap是线程不安全的?如何线程安全的使用HashMap?多并发情况下HashMap是否还会产生死循环?

为什么HashMap是线程不安全的? HashMap在并发执行put操作时会引起死循环,导致CPU利用率接近100%。因为多线程会导致HashMap的Node链表形成环形数据结构,一旦形成环形数据结构,Node的节点就...

魍宂庞
06/18
3
0
并发编程系列:ConcurrentHashMap的实现原理详解(JDK1.7和JDK1.8)

HashMap、CurrentHashMap 的实现原理基本都是BAT面试必考内容,我在历史博文《阿里P8架构师谈:深入探讨HashMap的底层结构、原理、扩容机制》中曾深入谈过hashmap的实现原理,今天主要谈Cur...

mikechen优知
2018/12/24
382
0
JDK7和JDK8concurrentHashmap区别

哈希表 1.介绍 哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值。 哈希的思路很简单,如果所有的键都是整数,那么就可以使用一个简...

osc_fa164jpo
2019/03/21
2
0

没有更多内容

加载失败,请刷新页面

加载更多

如何在控制器中使用过滤器? - How to use a filter in a controller?

问题: I have written a filter function which will return data based on the argument you are passing. 我编写了一个过滤函数,它将根据您传递的参数返回数据。 I want the same functi......

富含淀粉
23分钟前
14
0
android:layout_weight是什么意思? - What does android:layout_weight mean?

问题: I don't understand how to use this attribute. 我不明白如何使用这个属性。 Can anyone tell me more about it? 谁能告诉我更多关于它的事情? 解决方案: 参考一: https://stacko...

javail
53分钟前
17
0
CSS背景不透明度[重复] - CSS Background Opacity [duplicate]

问题: This question already has an answer here: 这个问题已经在这里有了答案: How do I give text or an image a transparent background using CSS? 如何使用CSS为文本或图像提供透明背...

fyin1314
今天
17
0
node http 获取gb2312网页如何转为utf8

最初,我想当然认为是下述做法,但被证明是错误的 const http = require('http'), iconv = require('iconv-lite');const url = 'http://xxx';http.get(url, function(res) { var bo......

高延
今天
24
0
动态规划:LC198.打家劫舍

题目描述: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入...

曦鱼violet
今天
30
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部