本文来自网易云社区

作者:张伟

关于HashMap在并发场景下的问题有很多人,很多公司遇到过!也很多人总结过,我们很多时候都认为这样都坑距离自己很远,自己一定不会掉入这样都坑。可是我们随时都有就遇到了这样都问题,坑一直都在我们身边。今天遇到了一个非线程安全对象在并发场景下使用的问题,通过这个案例分析HashMap 在并发场景下使用存在的问题(当然在这个案例中还有很多问题值得我们去分析,值得大家引以为戒。)通过分析问题产生都原因,让我们今后更好远离这个BUG。

代码如图所示,大家都应该知道HashMap不是线程安全的。那么   HashMap在并发场景下可能存在哪些问题?

  1. 数据丢失

  2. 数据重复

  3. 死循环

关于死循环的问题,在Java8中个人认为是不存在了,在Java8之前的版本中之所以出现死循环是因为在resize的过程中对链表进行了倒序处理;在Java8中不再倒序处理,自然也不会出现死循环。

对这个问题Doug Lea 是这样说的:

  1. Doug Lea writes:
  2.  
  3. "This is a classic symptom of an incorrectly synchronized use ofHashMap. Clearly, the submitters need to use a thread-safe
  4. HashMap. If they upgraded to Java 5, they could just useConcurrentHashMap. If they can't do this yet, they can use
  5. either the pre-JSR166 version, or better, the unofficial backport
  6. as mentioned by Martin. If they can't do any of these, they canuse Hashtable or synchhronizedMap wrappers, and live with poorer
  7. performance. In any case, it's not a JDK or JVM bug."
  8.  
  9. I agree that the presence of a corrupted data structure alone
  10. does not indicate a bug in the JDK.

首先看一下put源码

  1. public V put(K key, V value) {        if (table == EMPTY_TABLE) {
  2.             inflateTable(threshold);
  3.         }        if (key == null)            return putForNullKey(value);        int hash = hash(key);        int i = indexFor(hash, table.length);        for (Entry e = table[i]; e != null; e = e.next) {
  4.             Object k;            if (e.hash == hash && ((= e.key) == key || key.equals(k))) {
  5.                 V oldValue = e.value;
  6.                 e.value = value;
  7.                 e.recordAccess(this);                return oldValue;
  8.             }
  9.         }
  10.  
  11.         modCount++;
  12.         addEntry(hash, key, value, i);        return null;
  13.     } void addEntry(int hash, K key, V value, int bucketIndex) {        if ((size >= threshold) && (null != table[bucketIndex])) {
  14.             resize(2 * table.length);
  15.             hash = (null != key) ? hash(key) : 0;
  16.             bucketIndex = indexFor(hash, table.length);
  17.         }
  18.  
  19.         createEntry(hash, key, value, bucketIndex);
  20.     }   
  21.     void createEntry(int hash, K key, V value, int bucketIndex) {
  22.         Entry e = table[bucketIndex];
  23.         table[bucketIndex] = new Entry<>(hash, key, value, e);
  24.         size++;
  25.     }

通过上面Java7中的源码分析一下为什么会出现数据丢失,如果有两条线程同时执行到这条语句     table[i]=null,时两个线程都会区创建Entry,这样存入会出现数据丢失。

如果有两个线程同时发现自己都key不存在,而这两个线程的key实际是相同的,在向链表中写入的时候第一线程将e设置为了自己的Entry,而第二个线程执行到了e.next,此时拿到的是最后一个节点,依然会将自己持有是数据插入到链表中,这样就出现了数据 重复。通过商品put源码可以发现,是先将数据写入到map中,再根据元素到个数再决定是否做resize.在resize过程中还会出现一个更为诡异都问题死循环。这个原因主要是因为hashMap在resize过程中对链表进行了一次倒序处理。假设两个线程同时进行resize,

A->B 第一线程在处理过程中比较慢,第二个线程已经完成了倒序编程了B-A 那么就出现了循环,B->A->B.这样就出现了就会出现CPU使用率飙升。

在下午突然收到其中一台机器CPU利用率不足告警,将jstack内容分析发现,可能出现了死循环和数据丢失情况,当然对于链表的操作同样存在问题。

PS:在这个过程中可以发现,之所以出现死循环,主要还是在于对于链表对倒序处理,在Java 8中,已经不在使用倒序列表,死循环问题得到了极大改善。

下图是负载和CPU的表现:




下面是线程栈的部分日志:

  1. DubboServerHandler-10.172.75.33:20880-thread-139" daemon prio=10 tid=0x0000000004a93000 nid=0x76fe runnable [0x00007f0ddaf2d000]
  2.    java.lang.Thread.State: RUNNABLE
  3. at java.util.HashMap.getEntry(HashMap.java:465)
  4. at java.util.HashMap.containsKey(HashMap.java:449)
  5.  
  6. "pool-9-thread-16" prio=10 tid=0x00000000033ef000 nid=0x4897 runnable [0x00007f0dd62cb000]
  7.    java.lang.Thread.State: RUNNABLE
  8. at java.util.HashMap.put(HashMap.java:494)
  9.  
  10. DubboServerHandler-10.172.75.33:20880-thread-189" daemon prio=10 tid=0x00007f0de99df800 nid=0x7722 runnable [0x00007f0dd8b09000]
  11.    java.lang.Thread.State: RUNNABLE
  12. at java.lang.Thread.yield(Native Method)
  13.  
  14. DubboServerHandler-10.172.75.33:20880-thread-157" daemon prio=10 tid=0x00007f0de9a94800 nid=0x7705 runnable [0x00007f0dda826000]
  15.    java.lang.Thread.State: RUNNABLE
  16. at java.lang.Thread.yield(Native Method)

网易云大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者张伟授权发布

相关文章:
【推荐】 知物由学 | AI时代,那些黑客正在如何打磨他们的“利器”?

HashMap在并发场景下踩过的坑的更多相关文章

  1. Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%。再往后,每提高0.1%,优化难度成指数级增长了。哪怕是千分之一,也直接影响用户体验,影响每天上万张机票的销售额。 在高并发场景下,提供了保证线程安全的对象、方法。比如经典的ConcurrentHashMap,它比起HashMap,有更小粒度的锁,并发读写性能更好。线程安全的StringBuilder取代S

    Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%.再往后,每提高0.1%,优化难度成指数级增长了.哪怕是千分之一,也直接影响用户体验,影响每天上万张机 ...

  2. 并发场景下HashMap死循环导致CPU100%的问题

    参考链接:并发场景下HashMap死循环导致CPU100%的问题

  3. 面试官问:HashMap在并发情况下为什么造成死循环?一脸懵

    这个问题是在面试时常问的几个问题,一般在问这个问题之前会问Hashmap和HashTable的区别?面试者一般会回答:hashtable是线程安全的,hashmap是线程不安全的. 那么面试官就会紧接 ...

  4. 【转】记录PHP、MySQL在高并发场景下产生的一次事故

    看了一篇网友日志,感觉工作中值得借鉴,原文如下: 事故描述 在一次项目中,上线了一新功能之后,陆陆续续的有客服向我们反应,有用户的个别道具数量高达42亿,但是当时一直没有到证据表示这是,确实存在,并且 ...

  5. 高并发场景下System.currentTimeMillis()的性能问题的优化 以及SnowFlakeIdWorker高性能ID生成器

    package xxx; import java.sql.Timestamp; import java.util.concurrent.*; import java.util.concurrent.a ...

  6. HttpClient在高并发场景下的优化实战

    在项目中使用HttpClient可能是很普遍,尤其在当下微服务大火形势下,如果服务之间是http调用就少不了跟http客户端找交道.由于项目用户规模不同以及应用场景不同,很多时候可能不需要特别处理也. ...

  7. 高并发场景下System.currentTimeMillis()的性能问题的优化

    高并发场景下System.currentTimeMillis()的性能问题的优化 package cn.ucaner.alpaca.common.util.key; import java.sql.T ...

  8. C++高并发场景下读多写少的解决方案

    C++高并发场景下读多写少的解决方案 概述 一谈到高并发的解决方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也 ...

  9. C++高并发场景下读多写少的优化方案

    概述 一谈到高并发的优化方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也能很大的影响整体性能,本文从单模块下读 ...

随机推荐

  1. 可枚举接口的知识点(IEnumerable 接口)要使用foreach,就必须实现可枚举接口

  2. js 实现商品放大镜效果

    知识点,需熟悉下面属性及含义: offsetLeft           //获取元素相对左侧的距离 (计算是从最左侧边框外开始) offsetTop           //获取元素相对顶部的距离 ...

  3. Objective-C中的@dynamic与@synthesize的区别

    Objective-C中的@dynamic 转自:http://blog.csdn.net/haishu_zheng/article/details/12873151 一.@dynamic与@synt ...

  4. PCB测试点的设计要求

    测试点的设计要求:1.定位孔采用非金属化的定位孔 ,误差小于0.05mm.定位孔周围3mm不能有元件.2.测试点直径不小于0.8mm,测试点之间的间距不小于1.27mm,测试点离元件不小于1.27mm ...

  5. DML-删除

    方式一:使用delete一.删除单表的记录★语法:delete from 表名 [where 筛选条件][limit 条目数]二.级联删除[补充]语法:delete 别名1,别名2 from 表1 别 ...

  6. js 获取任意一个元素的任意一个样式属性的值

    //谷歌,火狐支持console.log(window.getComputedStyle(my$("dv"),null).left);//IE8支持console.log(my$( ...

  7. 浅析Vue原理(部分源码解析)

    响应式 Object.defineProperty Object.defineProperty(obj, prop, descriptor) // 对象.属性.描述符 Object.definePro ...

  8. 20181030NOIP模拟赛T3

    2017种树 2017共有N棵树从0到N-1标号.现要把这些树种在一条直线上,第i棵树的种植位置X[i]如下确定: X[0] = X[0] MOD L: X[i] = (X[i-1]*A+B) MOD ...

  9. 上白泽慧音(tarjan,图的染色)

    题目描述 在幻想乡,上白泽慧音是以知识渊博闻名的老师.春雪异变导致人间之里的很多道路都被大雪堵塞,使有的学生不能顺利地到达慧音所在的村庄.因此慧音决定换一个能够聚集最多人数的村庄作为新的教学地点.人间 ...

  10. 日期格式操作,在oracle和mysql中的实现

    oracle add_months(日期格式值 , 整数n)  当整数n=12时,代表一年,向后推迟一年,若n=-12代表回退一年 如 to_char(add_months(to_date('2018 ...