谈谈HashMap线程不安全的体现
HashMap的原理以及如何实现,之前在JDK7与JDK8中HashMap的实现中已经说明了。
那么,为什么说HashMap是线程不安全的呢?它在多线程环境下,会发生什么情况呢?
1. resize死循环
我们都知道HashMap初始容量大小为16,一般来说,当有数据要插入时,都会检查容量有没有超过设定的thredhold,如果超过,需要增大Hash表的尺寸,但是这样一来,整个Hash表里的元素都需要被重算一遍。这叫rehash,这个成本相当的大。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable, initHashSeedAsNeeded(newCapacity)); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);} |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } }} |
大概看下transfer:
- 对索引数组中的元素遍历
- 对链表上的每一个节点遍历:用 next 取得要转移那个元素的下一个,将 e 转移到新 Hash 表的头部,使用头插法插入节点。
- 循环2,直到链表节点全部转移
- 循环1,直到所有索引数组全部转移
经过这几步,我们会发现转移的时候是逆序的。假如转移前链表顺序是1->2->3,那么转移后就会变成3->2->1。这时候就有点头绪了,死锁问题不就是因为1->2的同时2->1造成的吗?所以,HashMap 的死锁问题就出在这个transfer()函数上。
1.1 单线程 rehash 详细演示
单线程情况下,rehash 不会出现任何问题:
- 假设hash算法就是最简单的 key mod table.length(也就是数组的长度)。
- 最上面的是old hash 表,其中的Hash表的 size = 2, 所以 key = 3, 7, 5,在 mod 2以后碰撞发生在 table[1]
- 接下来的三个步骤是 Hash表 resize 到4,并将所有的
<key,value>重新rehash到新 Hash 表的过程
如图所示:

1.2 多线程 rehash 详细演示
为了思路更清晰,我们只将关键代码展示出来
|
1
2
3
4
5
6
|
while(null != e) { Entry<K,V> next = e.next; e.next = newTable[i]; newTable[i] = e; e = next;} |
Entry<K,V> next = e.next;——因为是单链表,如果要转移头指针,一定要保存下一个结点,不然转移后链表就丢了e.next = newTable[i];——e 要插入到链表的头部,所以要先用 e.next 指向新的 Hash 表第一个元素(为什么不加到新链表最后?因为复杂度是 O(N))newTable[i] = e;——现在新 Hash 表的头指针仍然指向 e 没转移前的第一个元素,所以需要将新 Hash 表的头指针指向 ee = next——转移 e 的下一个结点
假设这里有两个线程同时执行了put()操作,并进入了transfer()环节
|
1
2
3
4
5
6
|
while(null != e) { Entry<K,V> next = e.next; //线程1执行到这里被调度挂起了 e.next = newTable[i]; newTable[i] = e; e = next;} |
那么现在的状态为:

从上面的图我们可以看到,因为线程1的 e 指向了 key(3),而 next 指向了 key(7),在线程2 rehash 后,就指向了线程2 rehash 后的链表。
然后线程1被唤醒了:
- 执行
e.next = newTable[i],于是 key(3)的 next 指向了线程1的新 Hash 表,因为新 Hash 表为空,所以e.next = null, - 执行
newTable[i] = e,所以线程1的新 Hash 表第一个元素指向了线程2新 Hash 表的 key(3)。好了,e 处理完毕。 - 执行
e = next,将 e 指向 next,所以新的 e 是 key(7)
然后该执行 key(3)的 next 节点 key(7)了:
- 现在的 e 节点是 key(7),首先执行
Entry<K,V> next = e.next,那么 next 就是 key(3)了 - 执行
e.next = newTable[i],于是key(7) 的 next 就成了 key(3) - 执行
newTable[i] = e,那么线程1的新 Hash 表第一个元素变成了 key(7) - 执行
e = next,将 e 指向 next,所以新的 e 是 key(3)
这时候的状态图为:

然后又该执行 key(7)的 next 节点 key(3)了:
- 现在的 e 节点是 key(3),首先执行
Entry<K,V> next = e.next,那么 next 就是 null - 执行
e.next = newTable[i],于是key(3) 的 next 就成了 key(7) - 执行
newTable[i] = e,那么线程1的新 Hash 表第一个元素变成了 key(3) - 执行
e = next,将 e 指向 next,所以新的 e 是 key(7)
这时候的状态如图所示:

很明显,环形链表出现了!!当然,现在还没有事情,因为下一个节点是 null,所以transfer()就完成了,等put()的其余过程搞定后,HashMap 的底层实现就是线程1的新 Hash 表了。
2. fail-fast
如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。
这个异常意在提醒开发者及早意识到线程安全问题,具体原因请查看ConcurrentModificationException的原因以及解决措施
顺便再记录一个HashMap的问题:
为什么String, Interger这样的wrapper类适合作为键? String, Interger这样的wrapper类作为HashMap的键是再适合不过了,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个field声明成final就能保证hashCode是不变的,那么请这么做吧。因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。
Reference:
1. http://hwl-sz.iteye.com/blog/1897468?utm_source=tuicool&utm_medium=referral
2. http://github.thinkingbar.com/hashmap-infinite-loop/
3. http://www.importnew.com/7099.html
谈谈HashMap线程不安全的体现的更多相关文章
- HashMap线程不安全的体现
前言:我们都知道HashMap是线程不安全的,在多线程环境中不建议使用,但是其线程不安全主要体现在什么地方呢,本文将对该问题进行解密. 1.jdk1.7中的HashMap 在jdk1.8中对HashM ...
- Java基础知识强化之集合框架笔记80:HashMap的线程不安全性的体现
1. HashMap 的线程不安全性的体现: 主要是下面两方面: (1)多线程环境下,多个线程同时resize()时候,容易产生死锁现象.即:resize死循环 (2)如果在使用迭代器的过程中有其他线 ...
- 谈谈HashMap与HashTable
谈谈HashMap与HashTable HashMap 我们一直知道HashMap是非线程安全的,HashTable是线程安全的,可这是为什么呢?先聊聊HashMap吧,想要了解它为什么是非线程安全的 ...
- 《windows核心编程系列》十五谈谈windows线程栈
谈谈windows线程栈. 当系统创建线程时会为线程预订一块地址空间区域,注意仅仅是预订.默认情况下预定的这块区域的大小是1MB,虽然预订这么多,但是系统并不会给全部区域调拨物理存储器.默认情况下,仅 ...
- Java:谈谈控制线程的几种办法
目录 Java:谈谈控制线程的几种办法 join() sleep() 守护线程 主要方法 需要注意 优先级 弃用三兄弟 stop() resume suspend 中断三兄弟 interrupt() ...
- HashMap的实现原理?如何保证HashMap线程安全?
A:HashMap简单说就是它根据建的hashcode值存储数据的,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历的顺序是不确定的. B:HashMap基于哈希表,底层结构由数组来实 ...
- (转载)两种方法让HashMap线程安全
HashMap不是线程安全的,往往在写程序时需要通过一些方法来回避.其实JDK原生的提供了2种方法让HashMap支持线程安全. 方法一:通过Collections.synchronizedMap() ...
- HashMap&线程
1.HashMap概念 HashMap是一个散列表,存储内容是键值对(key-value)的映射, HashMap继承了AbstractMap,实现了Map.Cloneable.java.io.Ser ...
- 简单谈谈HashMap
概述 面试Java基础,HashMap可以说是一个绕不过去的基础容器,哪怕其他容器都不问,HashMap也是不能不问的. 除了HashMap,还有HashTable跟ConcurrentHashMap ...
随机推荐
- Python中特殊函数和表达式lambda,filter,map,reduce
1.lambda:使用lambda表达式可以定义一个匿名函数 lambda表达式是一种简洁格式的函数.该表达式不是正常的函数结构,而是属于表达式的类型 (1)基本格式: lambda 参数,参数... ...
- 如何修改隐藏Zblog/WordPress默认后台登录地址
我相信很多博主站长都遇到过站点被暴力破解,虽然未被破解,但是经常收到那些尝试登录失败的邮件提醒也会心慌慌的.对于这种情况,最好的办法就是修改/隐藏我们的后台登录地址. 关于zblogASP后台登录地址 ...
- postfix 设置邮件头翻译,本域邮件不进行邮件头翻译,仅发送至外网的进行邮件头翻译?
postfix 设置邮件头翻译,本域邮件不进行邮件头翻译,仅发送至外网的进行邮件头翻译? 现在设置的 smtp_generic_maps = hash:/etc/postfix/generic sen ...
- 大神的P图过程!快来偷窥!
来自美国的艺术家James(@jameasons) 平时我们总是能看到一些大神合成出这样的图片, 但是他们P图的过程是怎样的,很多人都是不知道的. 接下来再看看这位大神的其他作品, 如果你看了上面视频 ...
- centos7中输入ifconfig出现ens33,没有eth0(转)
vmware安装的centos7中没有出现eth0网卡,也没有ip,不能上网,输入ifconfig后如下图 解决办法 1.编辑网卡的配置文件 vi /etc/sysconfig/network-scr ...
- 纯H5+c3实现表单验证
客户端验证是网页客户端程序最常用的功能之一,我们之前使用了各种各样的js库来进行表单的验证.HTML5其实早已为我们提供了表单验证的功能.至于为啥没有流行起来估计是兼容性的问题还有就是样式太丑陋了吧. ...
- Web Api 2 认证与授权 2
HTTP Message Handler 在 Web Api 2 认证与授权 中讲解了几种实现机制,本篇就详细讲解 Message Handler 的实现方式 关于 Message Handler 在 ...
- 【转】再讲IQueryable<T>,揭开表达式树的神秘面纱
[转]再讲IQueryable<T>,揭开表达式树的神秘面纱 接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个 ...
- hdu 6208(后缀自动机、或者AC自动机
题意:给你n个字符串,问你是否存在一个字符串可以从中找到其他n-1个字符串. 思路:其实很简单,找到最长的那个字符串对他进行匹配,看是否能匹配到n-1个字符串. 可以用AC自动机或者后缀自动机做,但是 ...
- kbmmw 的远程桌面功能
kbmmw 内置了远程桌面控制功能好几年了,好多同学居然不知道这特性,因为kbmmw 默认没有开放这个特性, 今天我就给大家说一下如何开放这个功能,并用官方自带例子说一下使用方法. 首先要开放这个特性 ...