- 死循环是在扩容时产生的,扩容分析可参考 JDK1.7 版本中 HashMap 扩容。
- 这里我们使用扩容后的结果进行分析。
前提
1.有两个线程同时在对一个 HashMap 进行扩容。
2.线程 1 进行扩容 while 循环,在第一次循环中执行完 e.next = newTable[i]
时时间片用完,此时线程 2 进行扩容且完成了扩容。
扩容代码:
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;
}
}
}
线程 2 扩容完成后的容器结构如下图:扩容
1.线程 1 进行扩容,在第一次循环中执行完 e.next = newTable[i]
时时间片用完,暂停往下执行,此时,线程 1 中的变量结构如下:
a
,next 指向 b
,a
和b
的 next 都指向 null2.线程 2 进行扩容,且扩容完成,此时线程 1 看到的原容器已经发生变化,因此 e 和 next 实际指向结构变为:
3.线程 1 继续执行
newTable[i] = e
,结构如下图:4.线程 1 继续执行
e = next
,此时 e 和 next 均指向 b
如下图:5.线程 1 再次进入循环,执行
Entry<K,V> next = e.next
此时 next 指向a
如下图:6.线程 1 执行
e.next = newTable[i]
,newTable[i] 现在已经是 a
因此本次没有结构变化。7.线程 1 执行
newTable[i] = e
,结构如下:8.线程 1 执行
e = next
,结构如下:9.线程 1 进入下一轮循环,执行
Entry<K,V> next = e.next
此时 next 指向 null 如下图:10.线程 1 执行
e.next = newTable[i]
,重点来了,此时 newTable[i] 对应的是 b
,因此 e.next 指向了 b
,也就是 a.next 指向了 b
,出现了环结构,如下图:11.线程 1 执行
newTable[i] = e
如下图:12.线程 1 执行
e = next
,e 指向 null,如下图:13.由于 e 指向了 null,因此循环结束,最终扩容后的容器结构如下:
可以看到,新的容器中有环结构,因此在后续使用过程中会出现死循环。