今天在看《java并发编程的艺术》这本书的时候看到作者提到HashMap在多线程并发的环境下调用put()有可能出现死循环,导致cpu100%的现象,看了下源码结合网上的分析说明下这种可能性。可能出现问题的地方是在扩容的时候.
1 | public V put(K key, V value) |
resize()方法本身没有问题,问题出在transfer(newTable);这个方法是用来移动oldTable里的数据到newTable里。
1 | /** |
下面分析可能出现的情况,假设原来oldTable里存放a1,a2的hash值是一样的,那么entry链表顺序是:
P1:oldTable[i]->a1->a2->null
P2:oldTable[i]->a1->a2->null
线程P1运行到(1)这行时,e=a1(此时a1.next=a2),继续运行到(2)时,next=a2(这时候线程1获取了e 和next结点的引用)。这个时候切换到线程P2,线程P2执行完这个链表的循环,即完成整个hashMap的复制,得到一个新的hashMap。如果恰a1,a2在新的table中的hash值又是一样的,那么此时的链表顺序是:
主存:newTable[i]->a2->a1->null
注意这个时候,a1,a2连接顺序已经反了。现在cpu重新切回P1,在(3)这行以后:e.next = newTable[i];即:
a1.next=newTable[i];
newTable[i]=a1;
e=a2;//()
开始第二次while循环(e=a2,next=a1):(因为这边是引用,所以从主存中可以看出a2的next是指向a1的,这也就是后面发生循环的原因所在.这一步是将a2的next指向了a1)
a2.next=newTable[i];//也就是a2.next=a1
newTable[i]=a2
e=a1; (又获取了a1,此时a1)
开始第三次while循环(e=a1,next=null)
a1.next=newTable[i];//也就是a1.next=a2
这个时候a1.next=a2,a2.next=a1,形成回环了,这样就造成了死循环,在get操作的时候next永远不为null,造成死循环。
可以看到很偶然的情况下会出现死循环,不过一旦出现后果是非常严重的,多线程的环境还是应该用ConcurrentHashMap。