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等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也能很大的影响整体性能,本文从单模块下读 ...
随机推荐
- 调整home和根分区大小
目标:将VolGroup-lv_home缩小到100G,并将剩余的空间添加给VolGroup-lv_root ============================================= ...
- 【转】Android之drawable state各个属性详解
我们在定义一个drawable的时候可以通过xml定义的drawable对象.它使得一个图片能在不同的状态下显示不同的图案,比如一个Button,它有pressed,focused,或者其它状态,通过 ...
- JQuery 学习总结及实例 !! (转载)
出自 new:http://www.jianshu.com/users/1967b163cb61/latest_articles 1.JQuery简介 普通JavaScript的缺点:每种控件的操作方 ...
- es6之函数扩展与对象扩展
一.函数扩展 1.参数默认值 参数有默认值,后面不可以再加没有默认值的变量.如以下test函数中,不可以加写成 function test(x,y="word",z){ } fun ...
- supervisord的配置
https://blog.csdn.net/xyang81/article/details/51555473 这位大佬写的很详细 你们可以去参考一下
- Node.js发布http服务
Node.js发布http服务 2018-11-09 09:43:03 Visit 0 简单服务 var http = require(\'http\'); http.createServer( ...
- [转]java中文乱码的解决
在基于Java的编程中,经常会碰到汉字的处里及显示的问题,比如一大堆乱码或问号. 这是因为JAVA中默认的编码方式是UNICODE,而中国人通常使用的文件和DB都是基于GB2312或者BIG5等编码, ...
- ARM 汇编指令集 特点5:ARM 多级指令流水线
1.为增加处理器指令流 的速度,ARM使用多级流水线. 就是举个例子: mov r1,#0 ,这条指令 分几个人做,一个人从存储器取指令,解码指令中用到的寄存器,寄存器运算. 这样三步 :如果一个人做 ...
- OC之block 和协议
一.BOLCK (一)简介 BLOCK是什么?苹果推荐的类型,效率高,在运行中保存代码.用来封装和保存代码,有点像函数,BLOCK可以在任何时候执行. BOLCK和函数的相似性:(1)可以保存代码(2 ...
- TortoiseGit —— 配置密钥
TortoiseGit 使用扩展名为ppk的密钥,而不是ssh-keygen生成的rsa密钥.使用命令ssh-keygen -C "邮箱地址" -t rsa产生的密钥在Tortoi ...