HashMap的线程安全

学习java的人都知道,HashMap是线程不安全的,不能在多线程环境下共享一个HashMap变量,在jdk1.7中的HashMap的实现,多线程下共享HashMap会导致死循环(有hash冲突的时候,链表上可能存在环导致死循环),但是在jdk8中对这个写法作了优化,不会导致死循环了,但是依然是线程不安全的,多线程下数据是不准确的。

这里不是想说HashMap为什么不安全,然后分析他的源码,然后ConcurrentHashMap是线程安全,继续分析它的源码,这里想要大概解释下,jdk是如何将HashMap变成线程安全的。

最简单直接的方法,对HashMap的所有操作都加上同一把互斥锁,即锁住整个HashMap,将所有的操作都变成串行的。其实这就是jdk的HashTable。它肯定是线程安全的,但是因为所有的操作都是串行的,并发度为1,所有的操作都是串行的,那么同一时间就只能允许一个线程执行,效率是很低的。所以对这种方式的优化就是提高并发度,就有了jdk的ConcurrentHashMap。

HashMap的基本结构并不难:table数组就是hash桶,然后每个hash桶使用链表来解决hash冲突的问题。

提高并发度最直观的就是别一次性锁住整个table,少锁一点,这样就允许多个线程同时操作了。所以最好的情况就是锁的粒度就是table元素,即锁只是锁住一个桶位,因为一个线程操作操作首先会先定位到桶位上,然后实际操作的实际是链表。但是有一个问题,HashMap中可是存储了整个桶的全局数据变量size的,这个变量绝对不能多线程修改,否则size就不准了,所以没办法做到只是锁住一个hash桶位。接下来就看jdk大神的表演。

jdk7中,引入Segemnt,将原来整个table进行拆分成不同小的segment,可以认为每个segment是一个原来的Map,table的是一个Segment为元素的数组。在对Map进行操作的时候需要两次hash,一次hash定位segment,然后对segment进行上锁,然后修改segment内的内容,而size也是Segment的属性。这样就增大了整个Map的并发度。这就是jdk7的ConcurrentHashMap

在jcu包中,有一些CAS的类型封装,如AtomicLong等,这些都是都是在死循环中CAS,所以说如果竞争特别大,就有可能一直在CAS,导致cpu飙升。所以就有了LongAddr,它尽可能去减少竞争,是因为内部有一个cell,如果是多线成修改,那么这个线程实际修改的是对应cell的值,而在get的时候,将所有的cell数据进行相加,就得到了一个最终的值。也正是因为这样,LongAddr是一个最终一致的结构,当一致性要求特别高的时候,没办法使用这个接口。参考:jdk中的CAS实现乐观锁 vs 数据库乐观锁_数据库cas乐观锁_georgesnoopy的博客-CSDN博客中的CAS部分。

而jdk8中的ConcurrentHashMap也是利用了LongAddr同样的方式来管理size变量。

在jdk8中,ConcurrentHashMap中已经没有Segment了,结构又回到了和HashMap一样。而且在修改Map的时候,首先是CAS修改数据,如果修改不成功,再对对应的桶位使用synchronized加锁(不要谈synchronized而色变,优化后的synchronized效率不必lock差,差的是灵活性),而对于size的修改就是利用LongAddr的思想,所以在java8的ConcurrenthashMap的实现中,可以发现LongAdder的实现相似的身影:

private transient volatile int cellsBusy;

private transient volatile long baseCount;

private transient volatile CounterCell[] counterCells;

在size()的时候,会将这个值进行累加,和LongAddr思想是一模一样的,实际中还有个baseCount,会将cell中的值利用CAS合并到baseCount中的,具体在什么时候合并,都是写细节的问题。包括cell也是有冲突的时候才会用到,没有冲突的时候CAS就累加baseCount成功了。

所以,ConcurrentHashMap也是一个弱一致的结构,强一致的场景慎用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值