分析扩容的核心代码
java
// 构建新数组
Entry[] newTable = new Entry[newCapacity];
// 迁移老数组数据到新数组
transfer(newTable, initHashSeedAsNeeded(newCapacity));
// 迁移完毕后,替换老数组
table = newTable;
java
// 迁移数据的过程
void transfer(Entry[] newTable, boolean rehash){
int newCapacity = newTable.length;
// 外层遍历数组
for (Entry<k,v> e:table){
// 遍历链表
// 2号线程走完第二次循环,完成迁移数据(如下图2所示)
// 1号线程走完第二次循环,发现指向的是A(如下图3所示)
// 1号线程走完第三次循环,完成迁移数据(如下图4所示)
while(null != e){
Entry<K,V> next = e.next;
// 1号线程执行到这里停止
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
图3
图4
JDK1.7中的HashMap是线程不安全的,可能会出现并发扩容的操作。
JDK1.7中的HashMap在迁移数据时,采用的是头插法,导致节点的next指针会有变化。
先迁移完的线程,可能会导致其他线程在扩容时,扩容到最后,将最开始的节点重新的插入到了头节点的位置,导致指针再次变化,从而形成了一个环形链表。