hashMap在多线程环境下,调用put方法出现的死循环是由于扩容时候resize方法导致的链表出现循环。
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;
}
}
}
假设,现在有线程1和线程2调用put方法操作
线程1: 读取hashmap,准备扩容,此时线程2介入
线程2:
调用resize方法执行扩容的时候,会去调用transfer方法,这个方法会将将原来的数组中的元素复制到新扩容的数组,
在这个过程中会改变链表的结构
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;
}
}
--------------------------------------------------
假设A,B重新计算hash值后,还在是同一个下表位置0
先看table[0]位置的修改
1. 将table[0] 放到 newTable[0]的位置
2. 先遍历到A结点,将结点A放到newTable[0]位置,并和之前newTable[0]构成链表关系
3. 在遍历到B结点,将结点B放到newTable[0]位置,和之前的newTable[0]构成链表关系
这个过程中 A->B 会改变为 B->A,如下图
线程1,此时进入扩容
线程1调用resize方法,将结点移动到新的数组,
但是对于线程1仍然是A.next = B ,遍历结点时候还是会从A开始遍历,当遍历到结点B时候,本来应该是B.next = null,但是由于线程B的改动 B.next = A ,这样在遍历结点的过程中就构成了死循环。
参考博客: