HashMap那些事之HashMap多线程扩容造成CPU100%(循环引用)

JDK1.7中HashMap采用的是头插法,那么在多线程进行扩容时可能会造成循环引用和数据丢失的问题,JDK1.8中采用的是尾插法就不会造成这个问题,但是JDK8仍然会有数据覆盖(见下一篇)

循环引用分析:

1.首先我们要明确JDK1.7中单线程是怎样扩容的:

扩容代码:

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }

(1)假设原来数组容量为2,我们现在要将扩容为4,先遍历数组找到节点e,再遍历数组位置上的链表,创建节点next,并将next指向e.next

(2)判断是否需要重新hash计算出新的数组索引i,并将e.next指向新数组索引位置

(3)将新数组索引位置i指向元素e

(4)将next赋值给e

(5)再次进入循环

(6)继续循环最终得到

2.接下来演示多线程条件下的扩容:

(1)首先明确,每个线程都会创建自己的新的数组【newTable】,假设有两个线程t1和t2

(2)现在t1执行到if(rehash),发生了线程切换,切换到了线程t2,线程t2完成了扩容得到如下数组

(3)现在切换到线程t1继续执行,获取到e1的新的数组索引i后,执行后面步骤,产生如下效果:

e.next = newTable[i]

newTable[i] = e

e = next;进入循环;Entry<K,V> next = e.next; e.next = newTable[i];

newTable[i] = e;

e = next;进入循环;Entry<K,V> next = e.next; e.next = newTable[i];

总结:为什么使用hashmap会造成死循环呢?

这个原因主要是因为hashMap在resize过程中对链表进行了一次倒序处理。假设两个线程同时进行resize, A->B 第一线程在处理过程中比较慢,第二个线程已经完成了倒序编程了B->A 那么就出现了循环,B->A->B.这样就出现了就会出现CPU使用率飙升。

数据丢失分析:

1.假设现在有两个线程A、B同时对下面这个HashMap进行扩容操作:

2.正常扩容后的结果是下面这样的:

3.但是当线程A执行到上面transfer函数的第11行代码时,CPU时间片耗尽,线程A被挂起。即如下图中位置所示:

4.此时线程A中:e=3、next=7、e.next=null

5.当线程A的时间片耗尽后,CPU开始执行线程B,并在线程B中成功的完成了数据迁移

6.重点来了,根据Java内存模式可知,线程B执行完数据迁移后,此时主内存中newTable和table都是最新的,也就是说:7.next=3、3.next=null。

随后线程A获得CPU时间片继续执行newTable[i] = e,将3放入新数组对应的位置,执行完此轮循环后线程A的情况如下:

7.接着继续执行下一轮循环,此时e=7,从主内存中读取e.next时发现主内存中7.next=3,于是乎next=3,并将7采用头插法的方式放入新数组中,并继续执行完此轮循环,结果如下:

8.执行下一次循环可以发现,next=e.next=null,所以此轮循环将会是最后一轮循环。接下来当执行完e.next=newTable[i]即3.next=7后,3和7之间就相互连接了,当执行完newTable[i]=e后,3被头插法重新插入到链表中,执行结果如下图所示:

上面说了此时e.next=null即next=null,当执行完e=null后,将不会进行下一轮循环。到此线程A、B的扩容操作完成,很明显当线程A执行完后,HashMap中出现了环形结构,当在以后对该HashMap进行操作时会出现死循环。

如有问题欢迎指正

参考链接:hashmap的线程不安全体现在哪里? - 知乎

原图片作者链接(很好):JDK1.7和JDK1.8中HashMap为什么是线程不安全的?_张先森的博客-CSDN博客_hashmap为什么线程不安全

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值