问题:
我们在工作中经常使用HashMap这个数据结构,每一个程序猿都被告诫,这个数据结构不是线程安全的,在单线程操作HashMap的情况下,程序不会问题,但是如果多线程操作同一个HashMap,会出现cpu使用率百分百的情况,这是为什么咧?
1、百度一下的话,网上文章会告诉你答案,cpu使用率达到100%是因为HashMap在多线程并发的情况下entryList出现了环导致的。
我们接下来探究,为什么HashMap在多线程并发的情况下entryList会出现了环:其实这一切来源于HashMap的扩容
举个例,这个例子来源一位大佬的分享:
如果两个线程同时对hashMap进行扩容,其中有个线程执行的速度稍微快一点,HashMap扩容代码如下:
for (int j = 0; j < src.length; j++) {
5: Entry e = src[j];
6: if (e != null) {
7: src[j] = null;
8: do {
9: Entry next = e.next;
// Thread1 STOPS RIGHT HERE
10: int i = indexFor(e.hash, newCapacity);
11: e.next = newTable[i];
12: newTable[i] = e;
13: e = next;
14: } while (e != null);
15: }
16: }
当线程一在第10行(绿色字体)的位置发生了上下文切换, 此时线程一已经设置了节点A(e1)和A的下一个节点是B(next1),如下图所示:
此时线程一没有移动任何节点,仅仅是分配了一个新的存储桶数组空间(Thread1 buckets)。
此时线程2对HashMap进行了扩容操作,扩容后的entryList,如下图所示 :
但是此时在线程1中,请注意,e1 和 next1 仍指向相同的节点。但实际上这个节点的指向关系发生了变化。最重要的是,下一个关系被逆转了。也就是说,当 线程1 启动时,它具有节点 A,其下一个节点为节点 B。现在,情况正好相反,节点 B 的下一个节点是节点 A。但是线程1对于线程二的操作并不知情。以下是当线程1获得cpu资源继续往下执行时采取的操作:
下一次迭代将把 A 放到存储桶 3 列表的前面(毕竟是下一个)。并将它的next节点分配给B,而此时B节点的next节点实际已经分配给了A,产生了环,如下图所示:
参考资料:
1、http://mailinator.blogspot.com/2009/06/beautiful-race-condition.html
2、https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6423457