深入理解java集合框架之---------HashMap集合
深入理解HaspMap死循环问题
由于在公司项目中偶尔会遇到HashMap死循环造成CPU100%,重启后问题消失,隔一段时间又会反复出现。今天在这里来仔细剖析下多线程情况下HashMap所带来的问题:
1、多线程put操作后,get操作导致死循环。
2、多线程put非null元素后,get操作得到null值。
3、多线程put操作,导致元素丢失。
死循环场景重现
下面我用一段简单的DEMO模拟HashMap死循环:
public class Test extends Thread
{
static HashMap<Integer, Integer> map = new HashMap<Integer, Integer>(2);
static AtomicInteger at = new AtomicInteger(); public void run()
{
while(at.get() < 100000)
{
map.put(at.get(),at.get());
at.incrementAndGet();
}
}
其中map和at都是static的,即所有线程所共享的资源。接着5个线程并发操作该HashMap:
public static void main(String[] args)
{
Test t0 = new Test();
Test t1 = new Test();
Test t2 = new Test();
Test t3 = new Test();
Test t4 = new Test();
t0.start();
t1.start();
t2.start();
t3.start();
t4.start();
}
反复执行几次,出现这种情况则表示死循环了:

接下来我们去查看下CPU以及堆栈情况:

通过堆栈可以看到:Thread-3由于HashMap的扩容操作导致了死循环。
正常的扩容过程
我们先来看下单线程情况下,正常的rehash过程
1、假设我们的hash算法是简单的key mod一下表的大小(即数组的长度)。
2、最上面是old hash表,其中HASH表的size=2,所以key=3,5,7在mod 2 以后都冲突在table[1]这个位置上了。
3、接下来HASH表扩容,resize=4,然后所有的<key,value>重新进行散列分布,过程如下:

在单线程情况下,一切看起来都很美妙,扩容过程也相当顺利。接下来看下并发情况下的扩容。
并发情况下的扩容
1、首先假设我们有两个线程,分别用红色和蓝色标注了。
2、扩容部分的源代码:
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
3、如果在线程一执行到第9行代码就被CPU调度挂起,去执行线程2,且线程2把上面代码都执行完毕。我们来看看这个时候的状态:

4、接着CPU切换到线程一上来,执行8-14行代码,首先安置3这个Entry:

这里需要注意的是:线程二已经完成执行完成,现在table里面所有的Entry都是最新的,就是说7的next是3,3的next是null;现在第一次循环已经结束,3已经安置妥当。看看接下来会发生什么事情:
1、e=next=7;
2、e!=null,循环继续
3、next=e.next=3
4、e.next 7的next指向3
5、放置7这个Entry,现在如图所示:

放置7之后,接着运行代码:
1、e=next=3;
2、判断不为空,继续循环
3、next= e.next 这里也就是3的next 为null
4、e.next=7,就3的next指向7.
5、放置3这个Entry,此时的状态如图:

这个时候其实就出现了死循环了,3移动节点头的位置,指向7这个Entry;在这之前7的next同时也指向了3这个Entry。
代码接着往下执行,e=next=null,此时条件判断会终止循环。这次扩容结束了。但是后续如果有查询(无论是查询的迭代还是扩容),都会hang死在table【3】这个位置上。现在回过来看文章开头的那个Demo,就是挂死在扩容阶段的transfer这个方法上面。
出现上面这种情况绝不是我要在测试环境弄一批数据专门为了演示这种问题。我们仔细思考一下就会得出这样一个结论:如果扩容前相邻的两个Entry在扩容后还是分配到相同的table位置上,就会出现死循环的BUG。在复杂的生产环境中,这种情况尽管不常见,但是可能会碰到
多线程put操作,导致元素丢失
下面来介绍下元素丢失的问题。这次我们选取3、5、7的顺序来演示:
1、如果在线程一执行到第9行代码就被CPU调度挂起:

2、线程二执行完成:

3、这个时候接着执行线程一,首先放置7这个Entry:

4、再放置5这个Entry:

5、由于5的next为null,此时扩容动作结束,导致3这个Entry丢失。
其他
这个问题当初有人上报到SUN公司,不过SUN不认为这是一个问题。因为HashMap本来就不支持并发。
如果大家想在并发场景下使用HashMap,有两种解决方法:
1、使用ConcurrentHashMap。
2、使用Collections.synchronizedMap(Mao<K,V> m)方法把HashMap变成一个线程安全的Map。
深入理解java集合框架之---------HashMap集合的更多相关文章
- Java集合框架:HashMap
转载: Java集合框架:HashMap Java集合框架概述 Java集合框架无论是在工作.学习.面试中都会经常涉及到,相信各位也并不陌生,其强大也不用多说,博主最近翻阅java集合框架的源码以 ...
- Java集合框架之HashMap浅析
Java集合框架之HashMap浅析 一.HashMap综述: 1.1.HashMap概述 位于java.util包下的HashMap是Java集合框架的重要成员,它在jdk1.8中定义如下: pub ...
- 牛客网Java刷题知识点之Java 集合框架的构成、集合框架中的迭代器Iterator、集合框架中的集合接口Collection(List和Set)、集合框架中的Map集合
不多说,直接上干货! 集合框架中包含了大量集合接口.这些接口的实现类和操作它们的算法. 集合容器因为内部的数据结构不同,有多种具体容器. 不断的向上抽取,就形成了集合框架. Map是一次添加一对元素. ...
- Java集合框架概述和集合的遍历
第三阶段 JAVA常见对象的学习 集合框架概述和集合的遍历 (一) 集合框架的概述 (1) 集合的由来 如果一个程序只包含固定数量的且其生命周期都是已知的对象,那么这是一个非常简单的程序. 通常,程序 ...
- 第14章 集合框架(1)-List集合的各种类
1.概述 1.1.Java集合框架的由来 1.2.什么是集合框架? 1.3.为什么需要集合框架 1.4.常用的框架接口规范 2.Vector类 2.1.存储原理 2.2.构造方法 2.3.常用方法 3 ...
- 深入理解Java并发框架AQS系列(一):线程
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.概述 1.1.前言 重剑无锋,大巧不工 读j.u.c包下的源码,永远无法绕开的经典 ...
- 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.AQS框架简介 AQS诞生于Jdk1.5,在当时低效且功能单一的synchroni ...
- 深入理解Java并发框架AQS系列(四):共享锁(Shared Lock)
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 深入理解Java并发框架AQS系列(三):独占锁(Exclusive Lock) 深入 ...
- (转)Java集合框架:HashMap
来源:朱小厮 链接:http://blog.csdn.net/u013256816/article/details/50912762 Java集合框架概述 Java集合框架无论是在工作.学习.面试中都 ...
随机推荐
- [翻译]Writing Custom Report Components 编写自定义报表组件
摘要:简单介绍了如何编写一个FastReport的组件,并且注册到FastReport中使用. Writing Custom Report Components 编写自定义报表组件 FastRep ...
- WinRT支持GB2312
在SL年代,有第三方类库支持 http://encoding4silverlight.codeplex.com 在RT版本有点不兼容,一直没时间看.今天闲的卵疼,就来一段代码兼容一下RT版本,迟点整合 ...
- Tomcat启动报错Invalid character found in method name. HTTP method names must be tokens
1.tomcat服务器需配置三个端口才能启动,安装时默认启用了这三个端口,当要运行多个tomcat服务时需要修改这三个端口,不能相同. 端口一: 修改http访问端口(默认为8080端口),配置文件为 ...
- PostgreSQL操作数据表
1.创建表(SysUser) create table "SysUsers"( "UserId" serial, --用户Id,自增 "LoginNa ...
- leetcode 罗马数字转整数
罗马数字包含以下七种字符:I,V,X,L,C,D 和M. 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 写做II ,即为两个并列的 1.1 ...
- mysql--MySQL数据库的简单认识
一.MySQL介绍 1.mysql版本 双授权版本:社区版(完全免费,功能也够nb了)和商业版(更好,功能更多更强大一些,但是收费,VIP,有售后服务,也会参考和吸收社区版的一些nb的功能,安全性和稳 ...
- “java.lang.NullPointerException”异常分析
1.父类定义的某个属性,没有被子类使用,或者在子类中,又重新定义一次. 2.因为调用了一个object的方法,且此object的reference为null:比如说:String a=null; // ...
- 20165219 Exp1 PC平台逆向破解
20165219 Exp1 PC平台逆向破解 实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串 ...
- “全栈2019”Java第一百一十一章:内部类可以被覆盖吗?
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- jzoj100029
陪审团制度历来是司法研究中的一个热议话题,由于陪审团的成员组成会对案件最终的结果产生巨大的影响,诉讼双方往往围绕陪审团由哪些人组成这一议题激烈争夺. 小 W 提出了一个甲乙双方互相制衡的陪审团成员挑选 ...