HashMap在并发情况下出现死循环的问题?

21 篇文章 1 订阅
3 篇文章 0 订阅
HashMap在Java 1.7中由于头插法在并发扩容时可能导致死循环,而在1.8后改用尾插法避免了这个问题,但仍不保证线程安全。即使不会出现死循环,多线程环境下HashMap的put和get操作仍可能导致数据不一致。因此,Java8的HashMap不适宜直接用于多线程环境,需要同步措施确保线程安全。
摘要由CSDN通过智能技术生成

HashMap的数据结构:

HashMap是用一个指针数组(假设为table[])来做分散所有的key,当一个key被加入时,会通过Hash算法通过key算出这个数组的下标i,然后就把这个<key, value>插到table[i]中,如果有两个不同的key被算在了同一个i,那么就叫冲突,又叫碰撞,这样会在table[i]上形成一个链表。我们知道,如果table[]的尺寸很小,比如只有2个,如果要放进10个keys的话,那么碰撞非常频繁,于是一个O(1)的查找算法,就变成了链表遍历,性能变成了O(n),这是Hash表的缺陷。

所以,Hash表的尺寸和容量非常的重要。一般来说,Hash表这个容器当有数据要插入时,都会检查容量有没有超过设定的阈值thredhold,如果超过,需要增大Hash表的尺寸,但是这样一来,整个Hash表里的无素都需要被重算一遍。这叫rehash,这个成本相当的大。(参考:疫苗:JAVA HASHMAP的死循环)

​ 参考文章:HashMap的为啥用尾插法?

​ JDK1.7的时候使用的是数组加链表的形式存储数据。在发生哈希碰撞的时候,就会产生一个节点,并且将这个节点添加在链表的表头,也就是头插法。在单线程的时候没有问题,但是并发的情况下多线程同时进行put操作,并且同时进行扩容的时候可能会出现链表环,导致死循环的发生。

​ 先说下扩容:扩容分为两步:

  • 扩容:创建一个新的Entry空数组,长度是原数组的2倍。
  • ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。

​ 因为扩容之后,数组的长度就变化了。然后根据hash的公式:index = HashCode(Key) & (Length - 1) 如果后面新添加元素,那就和之前的元素使用的hash函数不一样了。所以需要重新计算hash。重新计算索引位置之后,有可能原来在一个Entry链上的元素被放在了不同的位置。这时候如果是多线程的话就会形成A->B->A这样的循环链表的情况。这个时候去取值就会出现无限循环的状态。

那么JDK1.8之后为什么使用尾插法呢?

​ 使用尾插法在扩容时会保持链表元素原本的顺序。Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。

那是不是意味着Java8就可以把HashMap用在多线程中呢?
我认为即使不会出现死循环,但是通过源码看到put/get方法都没有加同步锁,多线程情况最容易出现的就是:无法保证上一秒put的值,下一秒get的时候还是原值,所以线程安全还是无法保证。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值