当HashMap有数据插入时,都会检查容量有没有超过设定的thredhold,如果超过就要做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, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (
int
)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY +
1
);
}
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的步骤:
- 1.对索引数组中的元素遍历
- 2.对链表上的每一个节点遍历:用 next 取得要转移那个元素的下一个,将 e 转移到新 Hash 表的头部,使用头插法插入节点。
- 3.循环2,直到链表节点全部转移
- 4.循环1,直到所有索引数组全部转移
经过这几步,我们会发现转移的时候是逆序的。假如转移前链表顺序是1->2->3,那么转移后就会变成3->2->1。这时候在多线程场景下,就会产生死锁问题了,因为可能会造成1->2的同时2->1。
关键代码分析:
while
(
null
!= e) {
Entry<K,V> next = e.next;
e.next = newTable[i];
newTable[i] = e;
e = next;
}
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 的下一个结点
假设HashMap某位的链表有两个对象,且有两个线程同时执行了put()
操作,并进入了transfer()
环节。 线程1在执行到第1步时挂起了,然后线程2先执行完毕。线程1继续执行。执行到转移到链表中第二个对象时,由于线程2已执行完,把对象2本应是null的next变成了对象1,于是transfer又继续执行转移对象1,于是对象1的next指向对象2,环形链表出现了。
详见http://www.importnew.com/22011.html
相关推荐
Java HashMap的死循环原因分析 HashMap是Java中一种常用的数据结构...HashMap的死循环问题是由resize操作引发的,而resize操作是HashMap的内部机制。为了避免死循环问题,我们应该使用ConcurrentHashMap代替HashMap。
从Java 1.7到1.8,HashMap经历了重大改进,尤其是在解决死循环问题上。本文将深入解析Java 1.8中HashMap的put方法源码,探讨其内部工作原理。 首先,我们了解HashMap的基本结构。在Java 1.8中,HashMap由数组加链表...
在多线程环境下,两个线程同时触发扩容可能导致循环链表的形成,从而引发死循环,这是一种严重的性能问题。 为了解决HashMap的线程不安全问题,我们可以采取以下几种策略: 1. 使用Collections.synchronizedMap()...
然而,在JDK1.7版本中,HashMap存在一个严重的问题,即“循环链表”(Looping List),这可能导致在多线程环境下性能急剧下降,甚至引发死循环。本文将深入探讨这个问题及其解决方案。 首先,我们来看看JDK1.7 ...
3. **死循环(死锁)**:在极端情况下,由于HashMap的迭代器依赖于table的状态,如果在迭代过程中table结构发生变化(比如resize),可能会造成迭代器陷入死循环。 为了解决这些问题,有以下几种策略: 1. **使用...
java7 hashmap源码 随着Java学习的不断深入,发现...多线程下,hashmap的resize()方法为什么容易出现死循环? 答: 其他面试题? 答: 并发 概述 :star::star: :star::star: 线程池 :star: AQS :star: 锁 ListenalbeFut
本文将详细解析HashMap的一些常见面试题,包括HashMap的长度为什么是2的幂次方、多线程操作下的死循环问题、底层实现以及扩容机制。 1. HashMap长度为什么是2的幂次方? 这个设计主要是为了优化哈希函数的效率。在...
5. 并发问题:HashMap在并发环境下可能导致死循环,因为1.7版本使用尾插法。1.8版本虽然使用头插法,但在并发情况下仍然不安全。面试中,你可能会被问到如何在并发环境中安全地使用HashMap,这时可以提到`HashTable`...
8. HashMap线程不安全体现在并发操作时可能出现数据丢失或死循环。JDK1.7的头部插入可能导致死循环,而JDK1.8改为了尾部插入以解决此问题。 9. 加载因子设置为0.75是考虑到容量与性能的平衡,太小可能导致频繁扩容...
但是 HashMap 原有的问题也都存在,比如在并发场景下使用时容易出现死循环。 final HashMap, String> map = new HashMap, String>(); for (int i = 0; i ; i++) { new Thread(new Runnable() { @Override public...
- **链表插入方式**:JDK 1.8前的头插法可能导致死循环,1.8后的尾插法避免了死循环,但可能导致值丢失。 - **解决方案**:使用线程安全的ConcurrentHashMap,其采用分段锁机制,提高并发性能。 4. **红黑树**: ...