导读:
在JDK1.7中,HashMap是一个由链表和数组组成的数据结构。当HashMap元素大于 数组长度*负载因子 时,HashMap会进行扩容,扩容大小为原大小2倍。
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
环形链表的形成分析:
进入resize(2 * table.length),找到transfer()方法 :
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;
}
}
}
根据上述代码看,扩容时链表元素顺序会翻转
A -> B -> null 扩容后 B -> A -> null
假定HashMap中 table[0] 中有元素
某一刻两个线程( T1, T2)同时操作该hashMap时,同时需要扩容。假设扩容时A和B的
h & (length-1) 仍相同。
当线程T1进入transfer方法, 第一次执行到 第5行时 这时记录 next=B , e=A。线程T1挂起。
线程T2进入方法并执行完成,这时HashMap的 table[0] 链表被T2修改为
线程T1重新开始执行, 对线程T1来说, next还是B, A.next = newTable[0] = null, 然后A被放入链表头, 链表变为A -> null;
T1再次进入循环, e = B, 因为 B.next 被 T2线程 修改为A, next = A, B.next = newTable[0] = A, 然后B被放入链表头, 链表变成:
正常的话现在应该退出循环,因为B.next被设置为A,再次进入循环。
e = A, next = null, 将A设置到链表头,A.next设置为 为newTble[0] 即 B, 将A放入链表头, 所以形成了闭环 A -> B -> A。
T1的e为null,正常退出不报错。
当执行get的时候,因为要遍历链表,进入死循环。