HashMap 将 cpu 打满始末

用户头像
林昱榕
关注
发布于: 2020 年 09 月 13 日

什么情况下使用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



发布于: 2020 年 09 月 13 日 阅读数: 779
用户头像

林昱榕

关注

开心生活,努力工作。 2018.02.13 加入

还未添加个人简介

评论

发布
暂无评论
HashMap将cpu打满始末