为什么HashMap是线程不安全的?

假设HashMap初始化大小为4,负载因子是1。已经先后插入c、b、a三个节点,当插入第四个节点时,HashMap就会扩容,并且对之前的三个节点rehash。以下是节点插入(头插法:每次插入链表的头部)的逻辑:

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
假设现在有两个线程同时进行,线程1和线程2,两个线程都会新建新的数组。
2
假设 线程2 在执行到 Entry<K,V>next=e.next; 之后,cpu时间片用完了,这时 变量e 指向 节点a变量next 指向 节点b。线程1继续执行,很不巧,a、b、c节点rehash之后又是在同一个位置7,开始移动节点。

第一步,移动节点a:
3
第二步,移动节点b:
4
第三步,移动节点c:
5
这个时候线程1的时间片用完,内部的table还没有完全生成新的newTable,线程2 开始执行,这时内部的引用关系如下:
6
这时,在 线程2 中,变量e 指向 节点a变量next 指向 节点b,开始执行循环体的剩余代码:

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;

执行以后的引用关系如下所示:
7
执行后,变量e 指向 节点b,因为e不是null,则继续执行循环体,执行后的引用关系:
8
变量e 又重新指回 节点a,只能继续执行循环体:
1. 执行完 Entry<K,V>next=e.next,目前 节点a 没有 next ,所以 变量next 指向 null
2. e.next=newTable[i] 其中 newTable[i] 指向 节点b,那就是把 节点anext 指向了 节点b,这样a和b就相互引用了,形成了一个环。
3. newTable[i]=e节点a 放到了数组下标i 位置。
4. e=next变量e 赋值为 null,因为第一步中 变量next 就是指向 null
9
节点a节点b 互相引用,形成了一个环。当在数组该位置get寻找对应的key时,就发生了死循环。另外,如果线程2把newTable 设置成到内部的table,节点c 的数据就丢了,看来还有数据遗失的问题。总之,千万不要在多线程写时使用HashMap,单写多读是没有问题的。


参考自:https://mp.weixin.qq.com/s/i_r1aLlQR8qTz7kz8vKk6w?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值