HashMap将cpu打满始末

什么情况下使用HashMap会导致cpu被打满呢?

一般来说cpu被打满,我们会有一个下意识的想法是程序出现了死循环,那死循环是怎么产生的呢?这种情况很诡异,正常的单线程情况下不会出现,所以,以造成诡异bug著称的多线程就得出来背锅了。

事实上,该问题的确是在多线程中不恰当使用HashMap导致的,因为HashMap不是线程安全的

下面通过举例分析多线程情况下,死循环的产生过程:

1、首先,该过程发生在HashMap进行容量扩容的过程,核心代码如下:

1:  // Transfer method in java.util.HashMap -
2:  // called to resize the hashmap
3:  
4:  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;
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: }

正常情况下(无线程竞争)的扩容过程

1)假设size=2的一个HashMap,准备扩容到size=4.

2)遍历数组中的每条链表,将元素插入到新数组的链表中,直至next=null:

多线程不同步情况下会产生死循环的场景

1)我们假设有T1和T2。两个线程同时调用扩容过程,也即上面那段代码。当T1执行完第9行,线程被切换:

1:  // Transfer method in java.util.HashMap -
2:  // called to resize the hashmap
3:  
4:  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: }

此时的HashMap的指针情况:e和next被设置指向如下(在e和next后面加1,代表T1线程):

2)紧接着,T2开始执行,并执行完整个扩容过程:

3)重点:此时T2将B指向了A,e1指向A,next1指向B。当T1恢复执行时,会将A的next指向B,循环指向产生。完整的执行过程如下:

以上就是HashMap将cpu打满的全过程,是线程安全的典型案例。解决方法很简单,就是做好同步。

参考资料:

A Beautiful Race Condition

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/bb568195178db540180ac5ffa
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券