多线程下HashMap的扩容导致线程安全性问题

JDK1.7中多线程扩容导致的循环链表

首先我们先来看一组单线程下扩容的例子:

单线程扩容(引用木霖森77大神博客):

假设:hash算法就是简单的key与length(数组长度)求余。
hash表长度为2,如果不扩容, 那么元素key为3,5,7按照计算(key%table.length)的话都应该碰撞到table[1]上

扩容:hash表长度会扩容为4
重新hash,key=3 会落到table[3]上(3%4=3), 当前e.next为key(7), 继续while循环
重新hash,key=7 会落到table[3]上(7%4=3), 产生碰撞, 这里采用的是头插入法,所以key=7的Entry会排在key=3前面(这里可以具体看while语句中代码)
当前e.next为key(5), 继续while循环
重新hash,key=5 会落到table[1]上(5%4=3), 当前e.next为null, 跳出while循环, resize结束

多线程扩容问题

我们截取jdk1.7的一部分代码


    /**
     * Transfers all entries from current table to newTable.
     */
    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;//线程1在这句话执行完成后挂起,cpu交给线程2进行处理
                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;
            }
        }
    }
  •  假如我们两个线程同时进行put操作,而线程1在执行完while循环的第一句话后,线程1挂起,然后由线程2 进行扩容处理。

  • 线程2扩容完成之后如上图这样
  • 然后现在开始执行线程1后面的代码,因为当时线程1已经对e和next已经赋完值了,所以现在的e和next就是这样的指向关系,如下图
  • 现在开始执行后面的代码
    • 第一次循环   e -> (key=3)     next->(key=7)
      • ①第一步:int i = indexFor(e.hash, newCapacity);     先计算e指向的(key = 3)的这个节点的需要在新的表中的位置 ,简单点计算就使用key%table.length取模来确定下标位置
      • ②第二步:e.next = newTable[i];   (这里需要注意这个newTable[i]是线程1的table表,线程1的table表是刚创建出来的,所以数组里面的东西为null,这个不是线程2的table表,在研究这个时候曾经我就出现了这种错误,当成了线程2的table来进行处理了),
      • 也就是给 (key=3) 的next指针指向了null,上面的图我已经画出了。
      • ③第三步:newTable[i] = e; 这一步又将(key=3)的这个节点赋值给了线程1的table表,那么此时的图应该是这样的
      • ④第四步:e = next; 将上面执行next的指针赋值给e,也就是e指向了(key=7)的节点。
  • 第二次循环 e->(key = 7)
    • ①第一步:Entry<K,V> next = e.next;  此时的e是指向7这个节点的,那么他的next节点接是(key = 3)
    • ②第二步:int i = indexFor(e.hash, newCapacity);  重新计算7这个节点的下标位置,key % table.length ,那么计算出来的就是3
    • ③第三步:e.next = newTable[i];   这个newTable[i] 就是线程1的table[3] 的这个位置,将(key=7)指向(key=3)这个节点
    • ④第四步:newTable[i] = e; 将线程1的table 表的第三个位置指向e也就是指向(key = 7)的这个位置
    • 那么此时指向关系应该是这样的
    • 整理的话就是这样的
    • ⑤第五步:e = next;   此时将(key=3)的节点重新赋值给e
  • 第三次循环:
    • ①第一步:Entry<K,V> next = e.next;    现在e指向了3  所以next = e.next = null
    • ②第二步:int i = indexFor(e.hash, newCapacity);  重新计算下标位置
    • ③第三步:e.next = newTable[i];   将线程1的table[3]的位置重新赋值为(key = 3)的next
    • 那么现在图应该是这样的
    • ④第四步:newTable[i] = e;   将e作为链表的头指针,那么此时图应该是这样的
    • 整理下如图
    • 同时(key=7)的next指针同时又指向了(key = 3)这样就形成了链式循环
    • ⑤第五步:e = next;   将null赋值给e,此时跳出循环,此时链式不会引发什么问题,但是当我们get(3)的时候这样就出现了get死循环

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值