自己理解的为什么hashmap线程不安全

HashMap的结构就是哈希表,底层是由数组加链表组成,这个数组中尽可能地分散所有的key,通过key的hash值得到数组下标,然后把entry插到该数组,假如有两个不同的key被分到相同的下标,也就是哈希冲突,那么该数组在该下标下就会形成链表。

如果链表长度太大,会进行扩容,也就是构建一个更长的数组来存放链表,链表里面的各个元素会根据哈希算法计算新的hashcode,也就是散列值,然后每个元素根据新的hashcode存放到新的数组的对应位置。

以上是哈希表的简介。

这里举例2种情况可能导致线程不安全,第二种情况讨论的更多一点。

1、多线程同时put

这个问题比较好想象,比如有两个线程A和B,首先A希望插入一个key-value对到HashMap中,首先计算记录所要落到的桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程A的时间片用完了,而此时线程B被调度得以执行,和线程A一样执行,只不过线程B成功将记录插到了桶里面,假设线程A插入的记录计算出来的桶索引和线程B要插入的记录计算出来的桶索引是一样的,那么当线程B成功插入之后,线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。


2、多线程扩容时

首先了解一下单线程里面,hashmap是 如何新建扩容的。

transfer方法是其中的核心内容。

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;  
            } 
        }  
    }  

这个方法的大意就是,会计算原来的桶(table)里面每个元素的新的hashcode,以此来判断他要放在那个新桶里面,放的方法是头插法:也就是把元素插入到新表指定位置的表头,如下图所示:

这里新的计算hashcode的方法为key mod 新表长度,比如key是3,新表长度为4,那么他在新表里面的索引就是1,如果key为5,他在新表里面的索引也是1。

比如一开始线程1刚把next元素赋值,就中断了,线程2也执行了扩容操作,并且成功了,那么此时结果如下:

线程1执行的时候被中断:

 

线程2完成执行了transfer:

之后线程1,恢复,继续执行,但是它里面的e和next的指向还是原来的,没有变,于是他继续根据transfer函数里面的内容来执行,也就是把e表示的元素插到新索引的链表的开头,然后e表示元素的next指向原来的开头,那么就会变成下面这样:

于是就形成了链表里面的环形结构,这样e的next永远都不会是null,也就会在transfer里面形成死循环。

 

参考:https://www.zhihu.com/question/28516433

https://www.jianshu.com/p/3ef6e9d26ef8

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值