HashMap的增删改查、扩容、哪些操作是线程不安全的

HashMap添加元素的过程

  • 初始化内部的Node[] table, 数组初始容量为16,负载因子为0.75, 数组扩容的阈值为(int)16*0.75
  • 计算key对应的数组中的位置, node = table[(table.length-1) & hash(key)]。如果node为空,则直接new一个node赋值给数组;否则就遍历node,如果遍历过程中发现 node.hash = hash(key) && key.equals(node.key),说明key重复则替换这个node的value,如果遍历到最后都没有重复的key,则把value赋值给最后一个node的next属性。
  • 赋值完成后,如果发现碰撞导致链表过长(大于等于TREEIFY_THRESHOLD=8),就把链表转换成红黑树,从而提高速度;
  • 如果发现table的容量超过了阈值,则执行扩容操作。

  •  

HashMap删除元素的过程

HashMap获取元素的过程

HashMap扩容过程

  • 如果table.length > 2的30次方,那么不会扩充数组,而只是把阈值扩大为Integer.MAX_VALUE,然后直接返回。
  • 新建一个容量是原来两倍的table,并且阈值也同时扩大两倍。
  • 把HashMap底层的table替换为新的table。
  • 把旧的table中的元素拷贝到新的table中。

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

  1. 当两个线程put的key计算出来在数组中的位置相同的时候,后一个线程的值可能会覆盖前一个线程的值。
  2. 扩容的时候,由于判断方式是++size > 阈值,所以当多个线程执行++size的时候,可能会++size的值不准确导致无法扩容
  3. 当多个线程进入扩容逻辑的时候,当第一个线程把扩容后的table和阈值赋值以后,第二个线程再进来,可能就会导致直接扩容四倍甚至更多倍。
  4. 删除元素的时候,是先找出要删除的node,然后再通过把前一个node的next属性指向被删除node的next属性删除的。如果这个过程中,被删除的node的值被别的线程重新更新过,那么删除之后这个值就不见了。

HashMap 1.7和1.8的区别


在Java 1.8的实现中,是通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在bucket的n比较小的时候,也能保证考虑到高低bit都参与到hash的计算中,同时不会有太大的开销。

在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。

 
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值