博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
HashMap在并发场景下踩过的坑
阅读量:6722 次
发布时间:2019-06-25

本文共 3654 字,大约阅读时间需要 12 分钟。

本文来自

作者:张伟

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

20180906101732ac65883a-fea7-4357-8fdb-dea3b50e7805.jpg  

 

    代码如图所示,大家都应该知道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-safeHashMap. If they upgraded to Java 5, they could just useConcurrentHashMap. If they can't do this yet, they can useeither the pre-JSR166 version, or better, the unofficial backportas mentioned by Martin. If they can't do any of these, they canuse Hashtable or synchhronizedMap wrappers, and live with poorerperformance. In any case, it's not a JDK or JVM bug."I agree that the presence of a corrupted data structure alonedoes 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的表现:

2018090610174025c9ced7-737e-4156-8bf0-861eb48123d0.jpg

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

DubboServerHandler-10.172.75.33:20880-thread-139" daemon prio=10 tid=0x0000000004a93000 nid=0x76fe runnable [0x00007f0ddaf2d000]   java.lang.Thread.State: RUNNABLE	at 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: RUNNABLE	at 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: RUNNABLE	at 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: RUNNABLE	at java.lang.Thread.yield(Native Method)

网易云大礼包

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

相关文章:

【推荐】 

转载地址:http://bqcmo.baihongyu.com/

你可能感兴趣的文章
三台机器实现免秘钥分发
查看>>
基于mongodb+node express的增删查改(CRUD)操作
查看>>
一句代码搞定点击空白处收键盘
查看>>
PHP动态属性和stdclass
查看>>
IBM P570查看配置
查看>>
如何在现有Fabric网络上添加一个Org?
查看>>
负载均衡集群介绍、LVS介绍、LVS调度算法、LVS NAT模式搭建
查看>>
Nginx服务监控
查看>>
C++一些标准模板容器简要介绍(2)
查看>>
博客测试
查看>>
dovecot并发数造成foxmail、outlook等客户端工具接收邮件有时候报错
查看>>
进程管理工具的使用
查看>>
mybatis第三天 小结
查看>>
phoneGap插件开发-多参数和返回值
查看>>
检测Gps和网络定位权限
查看>>
maven中使用springboot返回jsp和json数据
查看>>
GRASP设计模式
查看>>
利用ISA发布Outlook Anywhere邮件客户端
查看>>
IDEA Maven 中添加 Jetty插件
查看>>
双向循环链表
查看>>