ConcurrentHashMap学习

概述


ConcurrentHashMap绝对是编码的经典之作,里面的设计思想可以开拓我们的编程思路。想在高并发多线程环境使用一个Map的话,ConcurrentHashMap是首选,它不只性能高,还是线程安全的。

我们最常使用的HashMap是线程不安全的,可以参考HashMap多线程下发生死循环的原因


减小锁的粒度


HashTable虽然是线程安全的,但是所用的锁的粒度是容器级别的,在高并发的情况下,多个线程都在抢夺一把锁,抢不到锁的线程会被挂起,等着被再次调度,这样就有很多上下文切换的开销。

如果把Map里面的数据进行分段,每个段配置一把锁,在高并发的情况下,线程与线程之间修改的数据如果不是同一个段的,就可以各自修改各自段里面的数据。
这样多个线程就可以并发修改和访问不同段中的数据,无需获取容器级别的锁了,只需要获取段的锁
,大大提高了效率。

ConcurrentHashMap也是使用减小锁粒度这种方式来提高并发效率的。它将数据分段存储,每个段配置一把锁。


Segment段的定位


既然使用了分段存储数据的方式,那么无论是读取还是修改,第一步就是定位到对应的segment。ConcurrentHashMap会对key的hashcode进行再哈希,目的是减少hash冲突。然后使用下面的公式算出key对应的segment数组的下标:

(hash >>> segmentShift) & segmentMask
segment的长度设置

可以通过构造函数中的concurrencyLevel参数,并经过一定的算法得出segment的长度。

注意:如果Segment数组的长度太短,则带来更多的锁竞争,如果长度太长,则原本位于同一个Segment内的访问会扩散到不同的Segment中,会引起程序性能下降。


段里面的HashEntry数组中的元素定位


当成功的定位到segment后,还需要定位到segment中的Entry数组的下标。
segment里面存储的是一个HashEntry数组。

(table.length - 1) & hash

其中table指就是段中的HashEntry数组,使用上面的公式得到段中HashEntry数组的下标。无论是获取元素还是插入元素都是这样得到下标的。


数据结构图


这里写图片描述


put操作


1、先判断key对应的segment是否存在,如果不存在,则调用ensureSegment方法创建一个segment。

注意:ensureSegment方法在并发情况下可能有问题,但是该方法并没有使用加锁的办法来保证线程安全,而是结合CAS来保证创建一个segment的原子性。关于CAS的内容,可以参考学习一下CAS

2、 由于是写操作,先获取segment的锁,如果获取不到,不会使用互斥锁,而是使用自旋锁,尽量避免线程一开始就被挂起,先重试,一直到重试失败的次数到达阈值,当前线程才挂起。使用自旋锁可能是基于下面原因:

就算锁被其他线程获取了,也不会获取太久,很快会释放。

3、获取锁成功后,先判断是否有相同的key,如果有,则覆盖,没有则创建一个HashEntry,作为链表的头。如果segment里面的HashEntry的个数已经超过阈值,则进行段内的扩容(注意:不会对整个容器进行扩容)。
扩容的时候,为了不影响get操作,会创建新的HashEntry节点,旧的HashEntry节点仍然留着(有点copy-on-write的味道,请看线程安全的CopyOnWriteArrayList介绍)。
不过在理解Segment的扩容时,最好先了解一下HashMap的扩容流程,可以参考HashMap扩容

4、释放锁


get操作


get操作就比较简单了,有一点需要提的就是,整个get的过程是不需要加锁的,原因是HashEntry中的变量是volatile类型的:

volatile V value;
volatile HashEntry<K,V> next;

关于volatile的文章,可以参考学习volatile关键字


size操作


这个操作的实现也是有些难度的,原因是数据分段存储的,而且每个分段的元素也可能在不断的变化,ConcurrentHashMap使用如下思路进行处理:
1、首先不加锁循环所有的Segment,获得所有Segment的modcount之和。如果连续两次所有Segment的modcount和相等,则过程中没有发生其他线程修改ConcurrentHashMap的情况,返回获得的值。

2、如果modcount和上次不一样,则需要对所有segment加锁,然后进行统计。


参考的文章


  1. 深入分析ConcurrentHashMap
  2. ConcurrentHashMap之实现细节
  3. 构建一个更好的 HashMap
  4. 并发集合类
  5. 探索 ConcurrentHashMap 高并发性的实现机制
  6. Java Core系列之ConcurrentHashMap实现(JDK 1.7)
  7. ConcurrentHashMap总结
  8. ConcurrentHashMap实现原理——Java并发容器
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值