ConCurrentHashMap在jdk1.8与1.7的变化

主要改变为:

  1. 去除 Segment + HashEntry + Unsafe 的实现,改为 Synchronized + CAS + Node + Unsafe 的实现。 Node 和 HashEntry 的内容一样,但是HashEntry是一个内部类。
  2.  用 Synchronized + CAS 代替 Segment ,这样锁的粒度更小了,并且不是每次都要加锁了,CAS尝试失败了在加锁。
  3. put()方法中 初始化数组大小时,1.8不用加锁,将sizeCtl 这个变量置为-1,就表明table正在初始化。

1. put() 方法

JDK1.7

  • 需要定位 2 次 (segments[i],segment中的table[i])由于引入segment的概念,先通过key的 rehash值的高位 和 segments数组大小相与得到在 segments中的位置,然后在通过 key的rehash值 和 table数组大小相与得到在table中的位置
  • 没获取到 segment锁的线程,执行如下此操作:
  1. 确定table[i]的位置
  2. 通过首节点first遍历链表找有没有相同key
  3. 在进行1、2的期间还不断自旋获取锁,超过 64次 线程挂起!

JDK1.8

  • 先拿到根据 rehash值 定位,拿到table[i]的 首节点first,然后:
  1. 如果为null ,通过 CAS 的方式把 value put进去
  2. 如果非null ,并且 first.hash == -1 ,说明其他线程在扩容,参与一起扩容
  3. 如果非null ,并且 first.hash != -1 ,Synchronized锁住 first节点,判断是链表还是红黑树,遍历插入。

2. get() 方法

JDK1.7与jdk1.8类似

  • 由于变量 value 是由 volatile 修饰的,java内存模型中的 happen before 规则保证了 对于 volatile 修饰的变量始终是 写操作 先于 读操作 的,并且还有 volatile 的 内存可见性 保证修改完的数据可以马上更新到主存中,所以能保证在并发情况下,读出来的数据是最新的数据。
  • 如果get()到的是null值才去加锁。

3. resize() 方法

JDK1.7

        在 put() 元素时去做的扩容,获得了锁之后,在单线程中去做扩容(1.new个2倍数组 2.遍历old数组节点搬去新数组)。

JDK1.8

        jdk1.8的扩容支持并发迁移节点,从old数组的尾部开始,如果该桶被其他线程处理过了,就创建一个 ForwardingNode 放到该桶的首节点,hash值为-1,其他线程判断hash值为-1后就知道该桶被处理过了。

4. 计算size

JDK1.7

  1. 先采用不加锁的方式,计算两次,如果两次结果一样,说明是正确的,返回。
  2. 如果两次结果不一样,则把所有 segment 锁住,重新计算所有 segment的 Count 的和

JDK1.8

        没有segment的概念,只需要用一个 baseCount 变量来记录ConcurrentHashMap 当前节点的个数。

  1. 先尝试通过CAS 修改 baseCount
  2. 如果多线程竞争激烈,某些线程CAS失败,那就CAS尝试将 CELLSBUSY 置1,成功则可以把 baseCount变化的次数 暂存到一个数组 counterCells 里,后续数组 counterCells 的值会加到 baseCount 中。
  3. 如果 CELLSBUSY 置1失败又会反复进行CASbaseCount 和 CAScounterCells数组
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值