本文最后更新于5 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com
在JDK7之前使用HashMap的时候,如果在并发场景下会导致集合在扩容的时候形成环形链表,出现线程安全问题
问题出现的原因
当数组的size>数组长度*一个因子(默认为0.75)时会触发数组扩容,其实也就是新建一个数组+链表的数据结构(大小为Old数组的两倍),然后将数据进行拷贝,引用指向新的数组,在拷贝数据时会调用transfer方法
void transfer(Entry[] newTable) {
for (Entry<K,V> e : table) {
while (null != e) {
Entry<K,V> next = e.next; // (1) 关键点:先保存下一个节点
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i]; // (2) 头插法:将e的next指向新表的表头
newTable[i] = e; // (3) 将e放到新表的表头
e = next; // (4) 处理下一个节点
}
}
}
该方法会使用头插法对链表进行拷贝,会将链表反序,正常单线程操作下,这个操作是没有问题的,但是在并发场景下会出现线程安全问题,如下:
假设数组的大小为10个桶,现在已经装了9个,线程一和线程二同时进行put操作,触发hashmap的扩容机制,调用transfer方法,假设线程一正常进行put


线程一将1号桶的链表倒序
此时线程二也要调用transfer方法,但是呢由于线程一的操作使得B.next == A,在用头插法对A节点进行插入时,会导致A.next指向B,从而形成环形链表,导致死循环,在遍历时占满CPU

该问题在JDK8之后改用尾插法后得到解决,如果使用尾插法便不会改变链表的顺序,即使线程一已经完成了操作,有序链表的顺序不变,故对线程二的操作没有影响。





