HashMap多线程并发的问题
---恢复内容开始---
前言:大多数javaer都知道HashMap是线程不安全的,多线程环境下数据可能会发生错乱,一定要谨慎使用。这个结论是没错,可是HashMap的线程不安全远远不是数据脏读这么简单,它还有可能会发生死锁,造成内存飙升100%的问题,情况十分严重(别问我是怎么知道的,我刚把机器重启了一遍!)今天就来探讨一下这个问题,HashMap在多线程环境下究竟会发生什么?
一:模拟程序
温馨提示:咳咳,以下代码需在家长陪同下使用,非战斗人员请速速退场,否则带来的一切后果请自己负责!
言归正传,我们先来写个程序先:
import java.util.HashMap;
import java.util.Map; /**
* Created by Yiron on 3/30 0030.
*/
public class HashMapManyThread { static Map<String,String > map =new HashMap<String, String>(16);//初始化容量 public static class TestHashMapThread implements Runnable{ int start=0; public TestHashMapThread(int start){ this.start=start;
} @Override
public void run() { for (int i = 0; i <100000 ; i+=2) { System.out.println("--puting----"); map.put(Integer.toString(i),String.valueOf(Math.random()*100));
}
}
} public static void main(String[] args) throws InterruptedException { Thread[] threads =new Thread[100]; for (int i = 0; i <threads.length ; i++) { threads[i]=new Thread(new TestHashMapThread(i)); } for (int i = 0; i <100 ; i++) { threads[i].start();
} System.out.println(map.size());
}
}
上面的程序开了100个线程去访问给HashMap去put不同的值,如果是线程安全的,最后肯定会输出5000,可惜事与愿违,在尝试了几次以后,竟然程序给卡死了,紧接着打开任务管理器,发现cpu飙升至100%,而内存使用也有88%,简直丧心病狂!无奈下只能重启!
二:原因分析
在cmd中打开,然后输入jps,可以查看所有的java进程,然后可以看到所有的线程都在运行中,一直在无限循环状态,可以看到抛异常在at java.util.HashMap.put(HashMap.java:374)行,我们打开374行来看看:

以下是put方法的源码:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) { //374行
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;
}
可以看到这里是遍历数组的过程,其中遍历它的元素过程中,有一个e.next也就是指针往下移动,这里就很容易出现问题了。假如我们有两个线程Thread1和Thread2,假如在遍历的过程中,Thread1此时在链表的节点上e1,next指针会下一层指向e2;而此时Thread2遍历在e2节点上,它往回遍历next指针指向e1,那么此时的链表结构就被破坏了,形成了双向指针,构成了一个闭环(如图所示),就造成“死锁了”,我们来复习一下造成死锁的4个条件。

三:死锁的四个条件
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
我们来分析一下链表的互相引用符不符合上面四个条件:
①互斥条件:链表上的节点同一时间此时被两个线程占用,两个线程占用访问节点的权利,符合该条件
②请求和保持条件:Thread1保持着节点e1,又提出了占用节点e2(此时尚未释放e2);而Thread2此时占用e2,又提出了占用节点e1,Thread1占用着Thread2接下来要用的e1,而Thread2又占用着Thread1接下来要用的e2,符合该条件
③:不剥夺条件:线程是由自己的退出的,此时并没有任何中断机制(sleep或者wait方法或者interuppted中断),只能由自己释放,满足条件
④:环路等待条件:e1、e2、e3等形成了资源的环形链条,满足该条件
五:解决方法
5.1:使用Collections.synchronizedMap(Map map)方法,可以将HashMap变成一个同步的容器(拥有锁限制的同步机制)
static Map<String,String > map = Collections.synchronizedMap(new HashMap<String, String>());
synchronizedMap这个方法的原理的话,其实是把这个参数里面的hashMap注入到Collections的内部维护着的一个成员变量Map中,
final Object mutex;
public V put(K key, V value) {
synchronized(mutex) {return m.put(key, value);}
}
其中的mutex,是个不可变的成员变量,通过synchronized这个同步锁块就把整个代码锁住了,从而加上了同步规则。这个方法优点是简单粗暴,缺点就是性能不是很好,因为是阻塞的方式。
5.2:使用concurrentHashMap
static Map<String,String > map = new ConcurrentHashMap<String, String>();
这个方式是使用ConcurrentHashMap,它是线程安全的,
public V put(K key, V value) {
if (value == null)
throw new NullPointerException();
int hash = hash(key.hashCode());
return segmentFor(hash).put(key, hash, value, false);
}
V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();//上锁
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry<K,V>[] tab = table;
int index = hash & (tab.length - 1);
HashEntry<K,V> first = tab[index];
HashEntry<K,V> e = first;
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next;
V oldValue;
if (e != null) {
oldValue = e.value;
if (!onlyIfAbsent)
e.value = value;
}
else {
oldValue = null;
++modCount;
tab[index] = new HashEntry<K,V>(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock();
}
}
可以看到,concurrentHashMap的put方法是加锁的,它是同步的(采用了ReentrantLock可重入锁),可以保证线程安全。
六:总结
本文分析了HashMap在并发环境下的严重的问题,并没有我们想象中的那么轻易和简单,会造成的严重的cpu飙升问题,从而产生内存泄露,所以在多线程的环境下一定要慎重慎重!最好不要用,可以取而代之用ConcurrentHashMap,它的内部数据结构与HashMap迥然不同,可以保证线程安全。
HashMap多线程并发的问题的更多相关文章
- HashMap多线程并发问题分析
转载: HashMap多线程并发问题分析 并发问题的症状 多线程put后可能导致get死循环 从前我们的Java代码因为一些原因使用了HashMap这个东西,但是当时的程序是单线程的,一切都没有问题. ...
- HashMap多线程并发问题分析-正常和异常的rehash1(阿里)
多线程put后可能导致get死循环 从前我们的Java代码因为一些原因使用了HashMap这个东西,但是当时的程序是单线程的,一切都没有问题.后来,我们的程序性能有问题,所以需要变成多线程的,于是,变 ...
- java--HashMap多线程并发问题分析
并发问题的症状 多线程put后可能导致get死循环 从前我们的Java代码因为一些原因使用了HashMap这个东西,但是当时的程序是单线程的,一切都没有问题.后来,我们的程序性能有问题,所以需要变成多 ...
- Java 容器源码分析之HashMap多线程并发问题分析
并发问题的症状 多线程put后可能导致get死循环 从前我们的Java代码因为一些原因使用了HashMap这个东西,但是当时的程序是单线程的,一切都没有问题.后来,我们的程序性能有问题,所以需要变成多 ...
- Java - HashMap 多线程安全解析
HashMap多线程并发问题分析 多线程put后可能导致get死循环 从前我们的Java代码因为一些原因使用了HashMap这个东西,但是当时的程序是单线程的,一切都没有问题.后来,我们的程序性能有问 ...
- 多线程并发同一个表问题(li)
现有数据库开发过程中对事务的控制.事务锁.行锁.表锁的发现缺乏必要的方法和手段,通过以下手段可以丰富我们处理开发过程中处理锁问题的方法.For Update和For Update of使用户能够锁定指 ...
- Java面试题整理一(侧重多线程并发)
1..是否可以在static环境中访问非static变量? 答:static变量在Java中是属于类的,它在所有的实例中的值是一样的.当类被Java虚拟机载入的时候,会对static变量进行初始化.如 ...
- Java多线程-并发容器
Java多线程-并发容器 在Java1.5之后,通过几个并发容器类来改进同步容器类,同步容器类是通过将容器的状态串行访问,从而实现它们的线程安全的,这样做会消弱了并发性,当多个线程并发的竞争容器锁的时 ...
- 多线程并发 synchronized对象锁的控制与优化
本文针对用户取款时多线程并发情境,进行相关多线程控制与优化的描述. 首先建立用户类UserTest.业务操作类SynchronizedTest.数据存取类DataStore,多线程测试类MultiTh ...
随机推荐
- C++ 基础面试题-2
请写出一下程序的输出内容 /* ** 2018/03/21 22:02:03 ** Brief: ** Author:ZhangJianWei ** Email:Dream_Dog@163.com * ...
- Qt-QML-ComboBox-自定义,实现状态表示,内容可以动态正价,使用ListModel
哎呀呀呀, 问:杀死一个程序员一个程序要需要进步? 答:改三次需求 我感觉我就要再这需求的变更中被杀死了.不管怎么说,总是要跟着需求走的的,客户才是第一么(要不是因为钱,我才不会了) 下面先上个效果 ...
- DEDEcms调用当前栏目顶级栏目url地址
include/common.func.php 找到这个文件 在文件最下方加入以下代码: //获取顶级栏目url function GetTopTypeurl($id) { global $dsql; ...
- Objective-C 构造方法 分类 类的深入研究
构造方法 1.对象创建的原理 new的拆分两部曲 Person *p = [Person alloc]; 分配内存(+alloc) Person *p = [p init]; 初始化(-init) 合 ...
- Windows10安装GPU版本的Tensorflow
本人电脑配置(公司的)gtx1080ti,下载的的cuda8.0,cudnn6.0,python3.5.3安装完成后,安装tensorflow 1.pip install tensorflow-gpu ...
- 聊一聊session
最近从上家公司离职了,到了一家新公司,这几天一直在了解他们的项目,所以我自己的那个小项目也暂时搁浅了.. 今天差不多把他们的项目了解了,来院子写写我在这里边遇到的问题,影响最深刻的是seesion的. ...
- kvm网络虚拟化
网络虚拟化是虚拟化技术中最复杂的部分,学习难度最大. 但因为网络是虚拟化中非常重要的资源,所以再硬的骨头也必须要把它啃下来. 为了让大家对虚拟化网络的复杂程度有一个直观的认识,请看下图 这是 Open ...
- codeforces 269C Flawed Flow(网络流)
Emuskald considers himself a master of flow algorithms. Now he has completed his most ingenious prog ...
- Samba共享权限分配
案例推荐:http://www.cnblogs.com/mchina/archive/2012/12/18/2816717.html 本文不详细介绍全部参数,只介绍完成需求的一些参数. 需求: 1,账 ...
- Block的声明与定义语法
Block的声明 Block的声明与函数指针的声明类似 返回值类型(^变量名)(参数列表) Block的定义 ^返回值类型(参数列表) { 表达式 } 其中: 1 如果返回值类型是void,可以省略 ...