只是在jdk1.7以及多线程
下进行哈希扩容
的情况下才会发生
本质是产生了一个循环链表,导致在get或put新元素的时候一直循环遍历链表
主要发生在hashmap的transfer方法中
//转移原哈希表中的元素到新的哈希表中
void transfer(Entry[] newTable, boolean rehash) {
//获取新数组容量
int newCapacity = newTable.length;
//遍历老哈希表Entry[]数组
for (Entry<K,V> e : table) {
//当这个Entry不为空
while(null != e) {
//将e的下一个节点赋值给next
Entry<K,V> next = e.next;
//一般不需要重新计算哈希值
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//重新计算需要在新数组插入的数组下标
int i = indexFor(e.hash, newCapacity);
//将e元素插入到newTable[i]的头上,即将e的next属性指向newTable[i]
e.next = newTable[i];
//将e元素插入到newTable[i]上(头插法)
newTable[i] = e;
//开始转移下一个元素
e = next;
}
}
}
如果有两个线程对同一个hashmap同时开始扩容,进入到transfer方法
那么在各自线程中会分别生成一个新的Entry[] newTable
两个线程中的 e 和 next 都是局部变量,但是它们都指向了在堆内存当中的同一个对象
如图所示:
如果在第二个线程走到if(rehash)方法时发生了阻塞。
第一个线程正常执行,并无异常,执行结果:
然后线程2结束阻塞开始执行
此时的e2仍然指向堆内存当中的3号元素,next2也仍然指向堆内存中的2号元素,但是3号元素的next属性不再指向2号元素,而是指向了null.此时开始继续循环:
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;
}
第一次循环:将e2所指向的3号元素插入到新数组中,然后e2指向2号元素(因为此时next2指向2号元素),第一次循环结束
第二次循环:将e2指向的2号元素插入到新数组中(采用头插法),而此时的2号元素的next是指向3号元素的,所以在第二次循环结束之后,e2又指向了3号元素
第三次循环:将3号元素的next属性指向newTable[i],也就是当前在newTable[i]上的2号节点,然后将3号元素放到链表的头节点也就是newTable[i],此时就造成了循环链表的局面。