现象

大家可能都听过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.computeIfAbsent("AaAa",(String key) -> {map.put("BBBB", "value");return "value";});

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

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

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

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 loopnew 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~

不止 JDK7 的 HashMap ,JDK8 的 ConcurrentHashMap 也会造成 CPU 100%?原因与解决~相关推荐

  1. 不止JDK7的HashMap,JDK8的ConcurrentHashMap也会造成CPU 100%

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:朱小厮 公众号:朱小厮的博客(ID:hiddenkafka) ...

  2. jdk8的ConcurrentHashMap实现

    ConcurrentHashMap在jdk7的使用的是分段锁(ReentrantLock),而jdk8则改为使用synchronized.同时jdk8的ConcurrentHashMap和HashMa ...

  3. 集合之比较接口器+Map家族的HashMap+LinkedHashMap+Hashtable+ConcurrentHashMap

    集合之比较接口器+Map家族的HashMap+LinkedHashMap+Hashtable+ConcurrentHashMap 一.比较器接口 1.内置比较器 – Comparable import ...

  4. hashmap的特性?HashMap底层源码,数据结构?Hashmap和hashtable ConcurrentHashMap区别?

    1.hashmap的特性? 允许空键和空值(但空键只有一个,且放在第一位) 元素是无序的,而且顺序会不定时改变 key 用 Set 存放,所以想做到 key 不允许重复,key 对应的类需要重写 ha ...

  5. JDK7中HashMap源码分析

    文章目录 JDK7中的HashMap 一.JDK7中HashMap源码中重要的参数 二.JDK7中HashMap的构造方法 三.JDK7中创建一个HashMap的步骤 四.JDK7中HashMap的p ...

  6. Java中的Map集合及其子类HashMap,LinkedHashMap,TreeMap,ConcurrentHashMap

    一 .Map public interface Map<K,V> 将键映射到值的对象.一个映射不能包含重复的键:每个键最多只能映射到一个值.此接口哦取代了Dictionary类,后者完全是 ...

  7. HashMap 为什么会导致 CPU 100%?文章看不懂?

    来自:Java中文社群 无论是在实际工作中还是在面试中,HashMap 无疑是使用频率最高的知识点之一,所以我们需要搞懂每一个关于 HashMap 的知识点才行. 哈喽,大家好,我是老王,欢迎来到 J ...

  8. HashMap 为什么会导致 CPU 100%?文章看不懂?来看这个视频吧!——面试突击 006 期...

    无论是在实际工作中还是在面试中,HashMap 无疑是使用频率最高的知识点之一,所以我们需要搞懂每一个关于 HashMap 的知识点才行. 哈喽,大家好,我是老王,欢迎来到 Java 面试突击,我们今 ...

  9. JDK8之ConcurrentHashMap源码解读

    本文默认读者阅读过JDK8的HashMap源码,不再对源码中的红黑树操作进行分析. 本文主要对put().transfer().addCount()进行分析(replaceNode()源码近似于put ...

最新文章

  1. 哈希表的分类,创建,查找 以及相关问题解决
  2. 数据仓库入门(实验7)部署分析服务数据库
  3. 如何使用Java代码给图片增加倒影效果
  4. libsvm使用心得
  5. 贺TDSQL喜提286万QPS!本文回顾了它的十年锻造之路
  6. 搭建自己的博客(二十七):增加登录注册以及个人资料按钮
  7. 【输入法】Rime-中州韵 基本设置 附:官方定制指南
  8. 极大似然估计方法(Maximum Likelihood Estimate Method)
  9. 前端新手小白必看--最全静态网页模板网站
  10. Django开发教程 第一节 HelloWorld
  11. Windows PE (老毛桃) 介绍功能介绍
  12. RabbitMQ的使用(Java语言传统操作)
  13. c语言中除法与余数,带符号整数的除法与余数
  14. 一文带你复习计网中的重点知识(一万五千字长文)
  15. UIWebView中添加活动指示器,来化解用户等待心理
  16. 视觉学习笔记5——FAR Planner全局路径规划算法学习研究
  17. 武田宣布多项细胞疗法合作,以推进公司的新型免疫肿瘤学阵容
  18. python的控制结构之For、While、If循环问题
  19. 明星直播的品牌效应,这几个关键数据你一定要知道!
  20. 德国43000人汇聚线上黑客马拉松,齐愿合力干趴病毒

热门文章

  1. 06_Dart异常处理
  2. iOS应用代码注入防护
  3. C#实现枚举的相关操作
  4. eclipse中更改配置使得switch语句不出错
  5. win8.1下无法运行vc++6.0的解决方法
  6. Cassandra HBase和MongoDb性能比较
  7. 尝试优化骨骼动画计算的意外收获——使用嵌入式汇编对float转int进行优化
  8. footer固定到底部
  9. CS193P学习笔记(一)
  10. 使用U盘安装win7系统,遇到“无法定位现有系统分区”问题