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

代码如图所示,大家都应该知道HashMap不是线程安全的。那么 HashMap在并发场景下可能存在哪些问题?
- 数据丢失 
- 数据重复 
- 死循环 
关于死循环的问题,在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: 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)
网易云大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者张伟授权发布
相关文章:
【推荐】 知物由学 | AI时代,那些黑客正在如何打磨他们的“利器”?
HashMap在并发场景下踩过的坑的更多相关文章
- Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%。再往后,每提高0.1%,优化难度成指数级增长了。哪怕是千分之一,也直接影响用户体验,影响每天上万张机票的销售额。 在高并发场景下,提供了保证线程安全的对象、方法。比如经典的ConcurrentHashMap,它比起HashMap,有更小粒度的锁,并发读写性能更好。线程安全的StringBuilder取代S
		Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%.再往后,每提高0.1%,优化难度成指数级增长了.哪怕是千分之一,也直接影响用户体验,影响每天上万张机 ... 
- 并发场景下HashMap死循环导致CPU100%的问题
		参考链接:并发场景下HashMap死循环导致CPU100%的问题 
- 面试官问:HashMap在并发情况下为什么造成死循环?一脸懵
		这个问题是在面试时常问的几个问题,一般在问这个问题之前会问Hashmap和HashTable的区别?面试者一般会回答:hashtable是线程安全的,hashmap是线程不安全的. 那么面试官就会紧接 ... 
- 【转】记录PHP、MySQL在高并发场景下产生的一次事故
		看了一篇网友日志,感觉工作中值得借鉴,原文如下: 事故描述 在一次项目中,上线了一新功能之后,陆陆续续的有客服向我们反应,有用户的个别道具数量高达42亿,但是当时一直没有到证据表示这是,确实存在,并且 ... 
- 高并发场景下System.currentTimeMillis()的性能问题的优化 以及SnowFlakeIdWorker高性能ID生成器
		package xxx; import java.sql.Timestamp; import java.util.concurrent.*; import java.util.concurrent.a ... 
- HttpClient在高并发场景下的优化实战
		在项目中使用HttpClient可能是很普遍,尤其在当下微服务大火形势下,如果服务之间是http调用就少不了跟http客户端找交道.由于项目用户规模不同以及应用场景不同,很多时候可能不需要特别处理也. ... 
- 高并发场景下System.currentTimeMillis()的性能问题的优化
		高并发场景下System.currentTimeMillis()的性能问题的优化 package cn.ucaner.alpaca.common.util.key; import java.sql.T ... 
- C++高并发场景下读多写少的解决方案
		C++高并发场景下读多写少的解决方案 概述 一谈到高并发的解决方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也 ... 
- C++高并发场景下读多写少的优化方案
		概述 一谈到高并发的优化方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也能很大的影响整体性能,本文从单模块下读 ... 
随机推荐
- Dos小技巧-在Dos中直接打开软件
			在這裡我們舉一個例子 在D盤的D:\Program Files (x86)\Youdao\Dict4下面是關於有道字典的基本信息如圖(1.1.1).當點擊uninst.exe的時候顯示如圖:(1.1. ... 
- 剑指offer5 从尾到头打印链表
			错误代码: class Solution { public: vector<int> printListFromTailToHead(ListNode* head){ vector< ... 
- android 下使用Direct Texture
			要使用Direct Texture,需要有一份android系统的源码 部分C++代码如下: #include <stdio.h> #include <stdlib.h> #i ... 
- ARM  内核 汇编指令 的   8种 寻址方式
			str: store register ->指令将寄存器内容存到内存空间中, ldr: load register 将内存内容加载到通用寄存器, ldr/str 组合来实现ARM CPU 和内 ... 
- 如何在Mac中创建MiniKube
			转载请标明出处: http://blog.csdn.net/forezp/article/details/82563153 本文出自方志朋的博客 这篇文章介绍了如何在Mac系统中创建MiniKube. ... 
- sqlplus常用的几种登录方式
			1. sqlplus / as sysdba 操作系统认证,sys管理员登录,/后面要有空格. 2. sqlplus "/ as sysdba" 操作系统认证,sys管理员登录,/ ... 
- App升级iOS7体会
			本文转自App升级iOS7体会. xcode5 GM版已经发布,虽然还是pre-release版,但离最终版不远了.对于没有用到新特性的app面临的最大问题就是UI的变化.Apple提供了UI Tra ... 
- 复习宝典之Spring
			查看更多宝典,请点击<金三银四,你的专属面试宝典> 第六章:Spring Spring容器是Spring的核心,一切Spring bean都存储在Spring容器内,并由其通过IoC技术管 ... 
- mysql碰到的问题总结
			1.问题描述: 连接数据库出现大约10s延迟后才能连接,排除网络问题 解决方案: 通过抓包工具tcpdump抓包分析mysql在连接开始有近10s的空白请求,问题原因就在这 ,不知道在执行什么请求,后 ... 
- Xdebug 备注
			安装步骤: 查看自己的环境是否已安装 Xdebug ,查看方法:使用phpinfo(),搜索 Xdebug 如果没有 如图: 如果没有:下一步确定你的PHP版本信息: Xdebug下载地址 https ... 
