扩容源码
先来看一下扩容的源码
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//根据新的长度创建好的newTable
Entry[] newTable = new Entry[newCapacity];
// initHashSeedAsNeeded(newCapacity)这个返回值为false
// 转移
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
// 计算容量*负载因子
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
// newTable表示新创建的扩容后的数组
// rehash表示元素是否需要重新计算哈希值
void transfer(Entry[] newTable, boolean rehash) {
// 记录新数组的容量
int newCapacity = newTable.length;
// 遍历原数组的桶位置
for (Entry<K,V> e : table) {
// 如果桶位置不为空,则遍历链表的元素
while(null != 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.next = newTable[i];
// 新插入的元素成为桶位置的第一个元素
newTable[i] = e;
// 遍历原数组链表的下一个元素
e = next;
}
}
}
举例说明
假设有两个线程同时扩容,都需要数据迁移,线程A和B都执行到了Entry<K,V> next = e.next;
这一行
这时线程B的时间分片执行完了,而A继续执行并执行完整个扩容,此时的 table 如下图:
线程B分到了时间分片,继续执行
第一轮循环
e = C, next = B
e.next = newTable[i]; // newTable[i]是null,所以C的next是null
newTable[i] = e; // newTable[i]是C
e = next; // B
第二轮循环
e = B
Entry<K,V> next = e.next; // 从线程A扩容完得到的新的table可知,B的next是C
// 。。。
e.next = newTable[i]; // newTable[i]是C,所以B的next是C
newTable[i] = e; // newTable[i]是B
e = next; // C
第三轮循环
e = C
Entry<K,V> next = e.next; // 从线程A扩容完得到的新的table可知,C的next是null
// 。。。
e.next = newTable[i]; // newTable[i]是B,所以C的next是B
newTable[i] = e; // newTable[i]是C
e = next; // null
如图所示,已经形成了死循环