currenthashmap线程安全的原因

1 线程安全的hash表初始化

由上文可知ConcurrentHashMap是用table这个成员变量来持有hash表的。

table的初始化采用了延迟初始化策略,他会在第一次执行put的时候初始化table。

可通过sizeCtl保证线程安全。

成员变量sizeCtl在ConcurrentHashMap中的其中一个作用相当于HashMap中的threshold,当hash表中元素个数超过sizeCtl时,触发扩容; 他的另一个作用类似于一个标识,例如,当他等于-1的时候,说明已经有某一线程在执行hash表的初始化了,一个小于-1的值表示某一线程正在对hash表执行resize。

这个方法首先判断sizeCtl是否小于0,如果小于0,直接将当前线程变为就绪状态的线程。

当sizeCtl大于等于0时,当前线程会尝试通过CAS的方式将sizeCtl的值修改为-1。修改失败的线程会进入下一轮循环,判断sizeCtl<0了,被yield住;修改成功的线程会继续执行下面的初始化代码。

在new Node[]之前,要再检查一遍table是否为空,这里做双重检查的原因在于,如果另一个线程执行完#1代码后挂起,此时另一个初始化的线程执行完了#6的代码,此时sizeCtl是一个大于0的值,那么再切回这个线程执行的时候,是有可能重复初始化的。关于这个问题会在下图的并发场景中说明。

然后初始化hash表,并重新计算sizeCtl的值,最终返回初始化好的hash表。

下图详细说明了几种可能导致重复初始化hash表的并发场景,我们假设Thread2最终成功初始化hash表。 Thread1模拟的是CAS更新sizeCtl变量的并发场景 Thread2模拟的是table的双重检查的必要性

 

线程安全的put

put操作可分为以下两类 

当前hash表对应当前key的index上没有元素时 

当前hash表对应当前key的index上已经存在元素时(hash碰撞)

JDK8中直接用链表的头节点做为锁,ConcurrentHashMap通过这个锁的方式,使同一时间只有有一个线程对某一链表执行put,解决了并发问题。

线程安全的扩容

put方法的最后一步是统计hash表中元素的个数,如果超过sizeCtl的值,触发扩容。

扩容的代码略长,可大致看一下里面的中文注释,再参考下面的分析。 其实我们主要的目的是弄明白ConcurrentHashMap是如何解决HashMap的并发问题的。 带着这个问题来看源码就好。关于HashMap存在的问题,参考本文一开始说的笔者的另一篇文章即可。

其实HashMap的并发问题多半是由于put和扩容并发导致的。

这里我们就来看一下ConcurrentHashMap是如何解决的。

根据上述代码,对ConcurrentHashMap是如何解决HashMap并发问题这一疑问进行简要说明。

  • 首先new一个新的hash表(nextTable)出来,大小是原来的2倍。后面的rehash都是针对这个新的hash表操作,不涉及原hash表(table)。
  • 然后会对原hash表(table)中的每个链表进行rehash,此时会尝试获取头节点的锁。这一步就保证了在rehash的过程中不能对这个链表执行put操作。
  • 通过sizeCtl控制,使扩容过程中不会new出多个新hash表来。
  • 最后,将所有键值对重新rehash到新表(nextTable)中后,用nextTable将table替换。这就避免了HashMap中get和扩容并发时,可能get到null的问题。
  • 在整个过程中,共享变量的存储和读取全部通过volatile或CAS的方式,保证了线程安全

总结:

currenthashmap的线程安全保证主要通过synchronized,volatile,cas三种机制共同处理来保证整体的线程安全,线程安全问题主要出在put和扩容两方面。

put的时候会保证当前数组在该列的hash处已经锁定,并且没有出在扩容的前提下进行put,保证只有一个线程在该列put

扩容问题是通过cas和锁老保证的,首先会new出一个二倍大小的数组rehash不涉及原来数组,且对扩容列加锁并且可以实现并发扩容,将以扩完的列设置为不可用,最后都扩容完之后将新数组替换到table处。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值