多线程情况下,jdk1.7的HashMap不安全情况解读

目的:这篇文章纯以笔者的理解解读面试中一道高频题目——HashMap是线程安全的吗?如果不安全,为什么?

HashMap的底层数据结构是什么?

首先在jdk1.7中,HashMap的底层结构为数组+链表这一点毫无疑问,至于为什么选择数组,是由于数组属于顺序访问的集合,时间复杂度为O(1),我们通过对key进行hash散列后,再进行位与运算,可以快速的定位到key值所对应的下标,至于很多人会问为什么采用位与运算而不用取模元素,这一点其实跟位与的优秀性能有关系,试想一下,一个HashMap有几十万条数据,我们需要查询某个key的value,总不能hash方法花费大量的时间吧?还有一点,位与能确定key的下标吗?会不会越界?其实完全不用担心,因为底层源码是将数据的长度-1再位与key的hash值,所以数组的范围永远都是[0,数组长度-1],最后一个疑虑,hash值冲突了怎么办?开头就说了HashMap是数组+链表的结构,那么这个时候,一旦发现hash冲突(hash值一样),就会在数组中插入一个链表(其实每一个元素都是一个链表,只不过他们都是一个节点,后继为空),采用头插法(链表还有一种尾插法)的形式,将key值相同的元素,最新的value放在头结点,这样就达到了一个覆盖的效果。

HashMap的扩容机制?

一般我们声明HashMap的时候,若果指定了数组大小,就会根据我们的所指定的大小进行初始化,但是很多人会发现,在进行put方法以后,数组的容量变了,变成一个2的幂次数,这个是为什么?其实阅读源码就会发现,数组在put方法以后,大小会变成比其元素个数大的一个最低的2的幂次方数,这是因为在扩容的时候,由于采用的是低位的位与运算,从原先的数组拷贝到新的数组的时候,他们的下标不会变,因为2的幂次方-1与一个数,永远都是这个数,至于HashMap在什么时候扩容,这个是在数组的元素个数超过数组的长度乘以加载因子,一般加载因子是0.75,扩容就像刚才说的,会重新申请一个旧数组两倍大小的数组,将原先的内容拷贝过来,但是Hash只是要重新计算的。

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

前面两类问题不难理解,一般的面试中只需要理解加自己的表述基本上没问题,但是HashMap线程不安全就比较晦涩,首先,不安全肯定发生在多线程情况中,单线程就不用考虑这个问题,假定我们现在有两个线程在对HashMap操作,分别为T1和T2,现在T1线程对数组进行扩容,T1线程将下标为2的一个链表(链表的结构为e.next->next.next->null)进行复制,阅读源码我们得知,会先把链表的头结点作为e,下一个节点作为next,现在T1线程将e拷贝到新数组下标为2的位置,而后将next采用头插法的形式拷贝到链表的头结点,现在链表的结构为next.next->e.next->null(链表发生了倒置,这时候注意有一个next.next->e的指针),这时候线程切换,T2线程进来了,这个时候指针的指向还没有变,也就是说e被复制到新数组后,已经到了链表的尾部,开始之前他在链表的头部,T2把刚才的线程重新走了一遍,导致的结果就是e又回到了链表的头部,next为链表的尾部,并且下个节点为空,但此时注意链表中形成了一个e.next->next.next->null的指针,而T1线程形成了一个next.next->e的指针,综合的结果就是e.next->next.next->e,链表形成了一个环形指向,此时如果调用get方法,链表内部会形成死循环(链表的访问是迭代的),导致线程不安全

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值