一、先来看1.7 HashMap,里面的扩容代码
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
//代码1:获取e指向的Entry
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//代码2:获取下标
int i = indexFor(e.hash, newCapacity);
//代码3:e指向扩容后的下标对应的newTable
e.next = newTable[i];
//代码4:将e放入数组中,头插
newTable[i] = e;
//代码5:将next赋给e
e = next;
}
}
}
二、在单线程执行1.7 HashMap扩容时,我们假设下面是线程T1执行的扩容
1、进入while循环,执行到代码1:Entry<K,V> next = e.next;
假设代码2计算出i下标为2:
2、执行到代码3
3、执行到代码4
4、执行到代码5,至此。while循环第一次完成;
5、在旧的table,原数组节点上是a,这个链表执行完内while循环迁移以后:
总结,HashMap 1.7采用的是头部插入法,扩容后,在哈希冲突链表上,会将元素存储倒置;
三、那么在有多个线程执行,都执行到扩容时:
1、假设线程T2也来执行到扩容方法,但是在T2线程执行到while第一次循环,代码3:e.next = newTable[i],这之前发生了cpu调度,T2线程挂起;
T1线程执行完扩容以后,再回来执行T2的时候,那么就是现在下图中这样一个执行状态,e还是指向最开始的元素a,next指向元素b;
2、好了,T2线程回来了,他计算出来的下标i可能是3,newTable[3] ,从上次挂起的地方,开始继续执行代码3:e.next = newTable[i]
3、执行代码4:newTable[i] = e
4、执行代码5:e = next
5、开始第二次while循环,代码1:Entry<K,V> next = e.next
6、继续执行代码3、4、5:
7、while中第三次,最后造成了循环链表,发生死循环;