本文来自网易云社区

作者:张伟

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

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

  1. 数据丢失

  2. 数据重复

  3. 死循环

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

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

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

首先看一下put源码

public V put(K key, V value) {        if (table == EMPTY_TABLE) {inflateTable(threshold);}        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) {Object k;            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;e.value = value;e.recordAccess(this);                return oldValue;}}modCount++;addEntry(hash, key, value, i);        return null;} void addEntry(int hash, K key, V value, int bucketIndex) {        if ((size >= threshold) && (null != table[bucketIndex])) {resize(2 * table.length);hash = (null != key) ? hash(key) : 0;bucketIndex = indexFor(hash, table.length);}createEntry(hash, key, value, bucketIndex);}   void createEntry(int hash, K key, V value, int bucketIndex) {Entry e = table[bucketIndex];table[bucketIndex] = new Entry<>(hash, key, value, e);size++;}

通过上面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的表现:

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

DubboServerHandler-10.172.75.33:20880-thread-139" daemon prio=10 tid=0x0000000004a93000 nid=0x76fe runnable [0x00007f0ddaf2d000]java.lang.Thread.State: RUNNABLEat java.util.HashMap.getEntry(HashMap.java:465)at java.util.HashMap.containsKey(HashMap.java:449)"pool-9-thread-16" prio=10 tid=0x00000000033ef000 nid=0x4897 runnable [0x00007f0dd62cb000]java.lang.Thread.State: RUNNABLEat java.util.HashMap.put(HashMap.java:494)DubboServerHandler-10.172.75.33:20880-thread-189" daemon prio=10 tid=0x00007f0de99df800 nid=0x7722 runnable [0x00007f0dd8b09000]java.lang.Thread.State: RUNNABLEat java.lang.Thread.yield(Native Method)DubboServerHandler-10.172.75.33:20880-thread-157" daemon prio=10 tid=0x00007f0de9a94800 nid=0x7705 runnable [0x00007f0dda826000]java.lang.Thread.State: RUNNABLEat java.lang.Thread.yield(Native Method)

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

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

相关文章:
【推荐】 H5活动产品设计指南基础版
【推荐】 HBase原理–所有Region切分的细节都在这里了
【推荐】 从疑似华住集团4.93亿开房信息泄露 看个人如何预防信息泄露

转载于:https://www.cnblogs.com/zyfd/p/9596784.html

HashMap在并发场景下踩过的坑相关推荐

  1. 关于std::string 在 并发场景下 __grow_by_and_replace free was not allocated 的异常问题

    使用string时发现了一些坑. 我们知道stl 容器并不是线程安全的,所以在使用它们的过程中往往需要一些同步机制来保证并发场景下的同步更新. 应该踩的坑还是一个不拉的踩了进去,所以还是记录一下吧. ...

  2. 高并发场景下,到底先更新缓存还是先更新数据库?

    在大型系统中,为了减少数据库压力通常会引入缓存机制,一旦引入缓存又很容易造成缓存和数据库数据不一致,导致用户看到的是旧数据. 为了减少数据不一致的情况,更新缓存和数据库的机制显得尤为重要,接下来带领大 ...

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

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

  4. 高并发场景下数据库的常见问题及解决方案

    一.分库分表 (1)为什么要分库分表 随着系统访问量的增加,QPS越来越高,数据库磁盘容量不断增加,一般数据库服务器的QPS在800-1200的时候性能最佳,当超过2000的时候sql就会变得很慢并且 ...

  5. 高并发场景下缓存的常见问题

    作者介绍: 丁浪,非著名架构师.关注高并发.高可用的架构设计,对系统服务化.分库分表.性能调优等方面有深入研究和丰富实践经验.热衷于技术研究和分享. 声明:版权归丁浪作者本人所有,转载请联系作者本人 ...

  6. 本地缓存需要高时效性怎么办_缓存在高并发场景下的常见问题

    缓存一致性问题 当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副本中的数据也保持一致,不能出现差异现象.这就比较依赖缓存的过期和更新策略.一般会在数据发生更改 ...

  7. 分布式锁和mysql事物扣库存_这个是真的厉害,高并发场景下的订单和库存处理方案,讲的很详细了!...

    前言 之前一直有小伙伴私信我问我高并发场景下的订单和库存处理方案,我最近也是因为加班的原因比较忙,就一直没来得及回复.今天好不容易闲了下来想了想不如写篇文章把这些都列出来的,让大家都能学习到,说一千道 ...

  8. 读数据库遇到空就进行不下去_如何解决高并发场景下缓存+数据库双写不一致问题?...

    推荐阅读: 一只Tom猫:手撕分布式技术:限流.通讯.缓存,全部一锅端走送给你!​zhuanlan.zhihu.com 一只Tom猫:MySQL复习:20道常见面试题(含答案)+21条MySQL性能调 ...

  9. java高并发(二十一)高并发场景下缓存常见问题

    缓存一致性 当数据实时性要求很高时,需要保证缓存中的数据与数据库中的数据一致,缓存节点与副本中的数据一致,不能出现差异现象,这就比较依赖缓存的过期和更新策略了.一般会在数据发生更改的时候,主动跟新缓存 ...

  10. 高并发场景下的缓存有哪些常见的问题?

    作者 l 丁码农 来源:https://www.cnblogs.com/dinglang 一.缓存一致性问题 当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副 ...

最新文章

  1. ICCV 2019 | 加一个任务路由让数百个任务同时跑起来,怎么做到?
  2. 借条的注意事项,上面不能有这3个字
  3. html div 纵向居中,内容居中分为div内容水平居中与div内容垂直居中
  4. c语言变量加常量,C语言(二)---常量与变量(示例代码)
  5. python画图为什么运行不出来_PyCharm中Matplotlib绘图不能显示UI效果的问题解决
  6. 现在无法停止通用卷设备_济宁变压器油道设备
  7. 仿微信打飞机小游戏GamePlane(1)----概述
  8. 水经注地图下载器下载谷歌地图
  9. 软件工程实践 Blog5
  10. 文学-诗词-词人:词人
  11. Linux CentOS 7.2 排查系统木马
  12. 箴言:统计学的智慧七柱
  13. 如何将pdf转为word使用?
  14. 安装oracle12f 闪退,安装oracle ,调用图形界面java卡死,
  15. r语言list 转换成 vector
  16. Git (代码托管)
  17. 学术写作(Scientifi Academic Writing in English)
  18. linux中的画图软件inkscape无法正常运行解决
  19. 微信小程序用表格<table></table>查看数据
  20. ID自动生成(PHP)

热门文章

  1. 带通滤波器作用和用途_常见低通、高通、带通三种滤波器的工作原理
  2. GDB+coredump定位段错误
  3. 服务器上域名打不开网站,域名打不开网站
  4. HTML怎么写入形状,css3写各种形状(收集篇...)
  5. java list 排序_java 对list进行排序
  6. UGUI的ScrollRect
  7. Linux内核源码分析--内核启动之(1)zImage自解压过程(Linux-3.0 ARMv7)
  8. 3.2生产者和消费者(Producers and Consumers)
  9. 【渝粤教育】国家开放大学2018年秋季 0008-21T简明现代汉语 参考试题
  10. 【渝粤教育】21秋期末考试市场营销10256k2