03 ConcurrentHashMap

1. ConcurrentHashMap1.7和1.8版本实现原理
  • JDK1.7分段锁:1.7中,ConcurrentHashMap是由一个Segment数组和多个HashEntry数组组成,Segment数组中每一个元素存储的是HashEntry数组和链表,当对HashEntry数组的数据进行修改时, 必须首先获得与它对应的Segment锁。多个线程可以同时访问不同的Segment,从而使并发度提高,并发度与 Segment 的数量相等

  • JDK1.8Synchronized 和 CAS:1.8中,采用Node数组+链表+红黑树的数据结构来实现,并发控制改成使用 Synchronized 和 CAS 操作,对每个Node加锁,相比于1.7锁粒度更细。(插入元素时,如果桶为空,则cas插入元素,如果桶不为空,且当前该节点不处于移动状态,那么对该节点加synchronized锁,如果该节点hash>=0,则遍历链表更新节点或者插入新节点;如果该节点是TreeBin类型的节点,说明是红黑树结构,则通过putTreeVal方法往红黑树中插入节点)

  • 1.8开始采用尾插法,引入了红黑树。

2. ConcurrentHashMap有什么特点?(为什么保证线程安全且速度更快)

  ConcurrentHashMap1.8中是对每个Node加锁,并发控制使用Synchronized、volatile和CAS来操作,其中volatile,CAS等无锁技术可以减少锁竞争对于性能的影响,在线程安全的基础上提供了更好的写并发能力,和HashMap比更安全,和HashTable相比更高效。

3. ConcurrentHashMap的get过程?

首先计算hash值,定位到数组中桶的位置,再往下查找,整个get过程中不需要加锁,因为HashEntry 中的 val 属性用 volatile 关键词修饰,能够在线程之间保持可见性,所以每次获取时都是最新值,能够被多线程同时读,保证了线程安全和高效。

4. ConcurrentHashMap是怎么添加节点的?分1.7和1.8
  • 1.7:首先对要添加节点进行hash计算,定位到Segment,再通过一次hash计算,定位到HashEntry数组中的索引位置,遍历索引位置的链表,如果有节点存在相同的key,就替换该节点的value,没有的话,就将新节点插入到链表头部
  • 1.8:对要添加节点进行hash计算,定位到数组索引位置,遍历索引位置的链表,如果有节点存在相同的key,就替换该节点的value,没有的话,就将新节点插入到链表尾部

这里判断节点key是否相同,是先判断hash值,再用equals判断,两者都相等则表示key相等,这也是重写equals方法时也需要重写hashCode方法的原因。

5. ConcurrentHashMap的put过程?
  • key或value如果为空,抛空指针异常
  • table如果为空或length==0,初始化table
  1. 首先计算key的hash值,对应到桶的位置,如果桶为空,那就利用cas操作将节点加入到桶中。
  2. 如果桶不为空,且当前该节点不处于移动状态,那么对该节点加synchronized锁,如果该节点hash>=0,则遍历链表更新或插入节点;
  3. 如果该节点是TreeBin类型的节点,说明是红黑树结构,则通过putTreeVal方法往红黑树中插入节点
  4. 如果当前链表的个数达到8个,则通过treeifyBin方法转化为红黑树.
  5. 如果插入的是一个新节点,则执行addCount()方法尝试更新元素个数baseCount,并且检查是否需要扩容。
6. ConcurrentHashMap怎么计算size?
  • HashMap 定义了一个 modCount 变量,每次变动时,无论是 put 还是 remove ,都将 modCount 加1。遍历两次数组,如果得出的 modCount 值一样,就表示未变动了,成功返回 size。否则就表示又变动过了,就继续遍历再次比较 modCount。
  • ConcurrentHashMap 1.7:首先以不加锁的方式,定义了一个 modCount 变量,每次变动时,无论是 put 还是 remove ,都将 modCount 加1。遍历两次数组,如果得出的 modCount 值一样,就表示未变动了,成功返回 size。否则就再遍历一次,如果依然不一致,就对所有的 Segment 加锁,然后一个一个遍历,准确的求出 size。
    (ConcurrentHashMap在求size时首先会使用不加锁的模式去尝试多次计算ConcurrentHashMap的size,最多三次,比较前后两次计算的结果,如果一致就认为当前没有元素加入,计算的结果是准确的,然后返回结果。如果第一种方案不符合,他就会给每个Segment加上锁,然后计算size并返回。)
  • ConcurrentHashMap 1.8:新值了 mappingCount 方法,它的返回值类型是 long 类型,不会因为 size 方法是 int 类型而限制最大值。它里面有一个 volatile 修饰的 baseCount 变量,当没有发生冲突时,使用 baseCount 来计数, 还有一个填充单元 CounterCell 数组,当并发产生冲突时,使用 CounterCell 计数,最后通过 baseCount 和 CounterCell 数组总的计数值来得到 size 大小。其中 CounterCell 添加了 @Contended 注解来防止伪共享,伪共享产生的原因是因为缓存系统是以缓存行为单位进行存储的,缓存行是 2 的幂次方的连续字节,一般为64个字节,当多线程修改同一个缓存行中不同的变量时,由于同时只能有一个线程操作缓存行,所以会影响彼此的性能。JDK1.8 通过添加注解 @Contended 使变量在缓存行中分隔开来解决伪共享问题。(在JDK1.8之前是通过数据填充的方式来解决)(得出的 size 并不准确)
    (1.8中使用一个volatile类型的变量baseCount和一个CounterCell数组记录元素的个数,通过累加baseCount和CounterCell数组中的数量,即可得到元素的总个数。
    为啥有baseCount和CounterCell?
    在一个低并发的情况下,就只是简单地使用CAS操作来对baseCount进行更新,但只要这个CAS操作失败一次,就代表有多个线程正在竞争,那么就转而使用CounterCell数组进行计数,数组内的每个ConuterCell都是一个独立的计数单元。)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值