常用集合※【HashMap(二)】
来源:互联网 发布:武力统一台湾后果知乎 编辑:程序博客网 时间:2024/06/08 09:33
下面通过一个简单的多线程例子来说明一下HashMap是线程不安全的。
public class HashMapTest2 {public static final HashMap<String, String> firstHashMap = new HashMap<String, String>();public static void main(String[] args) throws InterruptedException {// 线程一Thread t1 = new Thread() {public void run() {for (int i = 1; i < 20; i++) {firstHashMap.put(String.valueOf(i), String.valueOf(i));}}};// 线程二Thread t2 = new Thread() {public void run() {for (int j = 20; j < 40; j++) {firstHashMap.put(String.valueOf(j), String.valueOf(j));}}};t1.start();t2.start();// 主线程休眠1秒钟Thread.currentThread().sleep(1000);for (int n = 1; n < 40; n++) {// 如果key和value不同,说明在两个线程put的过程中出现异常。if (!String.valueOf(n).equals(firstHashMap.get(String.valueOf(n)))) {System.err.println(String.valueOf(n) + ":"+ firstHashMap.get(String.valueOf(n)));}}}}demo运行时,有时会报如下异常:(因为是循环存放数据,不应该出现空值)。
看过HashMap源码的都知道HashMap初始容量大小为16,一般来说,当有数据要插入时,都会检查容量有没有超过设定的thredhold,如果超过,需要增大Hash表的尺寸,但是这样一来,整个Hash表里的元素都需要被重算一遍。这叫rehash,这个成本相当的大。
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); table = newTable; threshold = (int)(newCapacity * loadFactor); } 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); } } }
1.对索引数组中的元素遍历
2.对链表上的每一个节点遍历:用 next 取得要转移那个元素的下一个,将 e 转移到新 Hash 表的头部,使用头插法插入节点。
3.循环2,直到链表节点全部转移
4.循环1,直到所有索引数组全部转移
经过这几步,我们会发现转移的时候是逆序的。假如转移前链表顺序是1->2->3,那么转移后就会变成3->2->1。这时候就有点头绪了,死锁问题不就是因为1->2的同时2->1造成的吗?所以,HashMap 的死锁问题就出在这个transfer()函数上。
造成死锁的关键代码
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);
1.Entry<K,V> next = e.next;——因为是单链表,如果要转移头指针,一定要保存下一个结点,不然转移后链表就丢了
2.e.next = newTable[i];——e 要插入到链表的头部,所以要先用 e.next 指向新的 Hash 表第一个元素(为什么不加到新链表最后?因为复杂度是 O(N))
3.newTable[i] = e;——现在新 Hash 表的头指针仍然指向 e 没转移前的第一个元素,所以需要将新 Hash 表的头指针指向 e
4.e = next——转移 e 的下一个结点。
假设初始状态空间就两个,元素存放的位置如下。
现在有两个线程同时执行了Put()操作,并进入了transfer()环节。线程1在执行Entry<K,V>next=e.next时被调度了起来。
do { Entry<K,V> next = e.next; //线程1被调度了 int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null);
线程2开始执行,这时的状态图,应该是如下的:
这时线程1唤醒,线程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 表了。put()过程虽然造成了环形链表,但是它没有发生错误。它静静的等待着get()这个冤大头的到来
参考博文:
http://github.thinkingbar.com/hashmap-infinite-loop/
- 常用集合※【HashMap(二)】
- 常用集合※【HashMap(一)】
- java1.8 常用集合源码学习:HashMap
- java集合框架(二)-HashMap总结
- 深入java集合系列:HashMap 二
- HashMap集合
- HashMap集合
- HashMap集合
- HashMap集合
- 集合-HashMap
- 常用集合ArrayList,LinkedList,HashMap,HashSet源码分析
- 常用集合ArrayList,LinkedList,HashMap,HashSet源码分析
- Java 集合ArrayList ,HashMap常用的循环方式
- Java中最常用的集合类框架之 HashMap
- 集合二:Set、HashSet、TreeSet、Map、HashMap、TreeMap
- 深入Java集合学习系列二:HashMap的实现原理
- JAVA学习笔记(二十二)- 集合HashMap与Hashtable
- 【Java基础之集合(二)】Java中HashMap详解
- Microsoft Office Professional Plus 2010在安装中出错 解决办法 安装office2010出错
- Ruby 语言要点
- Android系统时间
- play框架2.5.6教程——设置你喜欢的IDE
- My tips----搭建一个自己喜欢的风格桌面
- 常用集合※【HashMap(二)】
- 腾讯笔试编程题--构造回文
- 15个实用的PHP正则表达式
- 百度地图生成器
- 【iOS】关于DES的加密
- Struts2 菜鸟使用小记录
- myeclipse中自带的tomcat在安装文件中的具体位置
- MongoDB分组
- Exchange Server 2013部署 windows server 2008 r2