ConcurrentHashMap原理解析
【什么是ConcurrentHashMap?】
众所周知,Hashmap是一种非常高效的数据结构,但是依旧有它的缺陷。那就是在并发插入数据时,有可能会出现带环链表,让下一次的读操作出现死循环。于是为了避免HashMap的线程安全问题,ConcurrentHashMap应运而生。
【ConcurrentHashMap原理】
ConcurrentHashMap是一个二级hash表,在一个总的hash表下面有若干子hash表。
我们将子hash表称为Segment。
那么采用这种结构有什么好处呢?
我们知道解决线程安全问题的方法,就是加锁。当时给整体加锁的话,又会使得效率急剧下降。在ConcurrentHashMap的结构下我们就可以采用【锁分段技术】,每个Segment就是一个自治区,读写操作高度自治,Segment间互不影响。
ConcurrentHashMap解决线程安全问题的具体场景:
场景1:不同Segment的读与读,读与写,写与写
可以并发执行
场景2:同一Segment的写与读
可以并发执行
场景3:同一Segment的并发写入
加锁
因此,采用给单个的Segment加锁,相比于整体加锁就极大地提高了效率,同时保证了线程安全。
【ConcurrentHash方法】
【get方法】
- 为输入的key值,进行hash运算得到hash值
- 通过hash值定位到对应的Segment
- 再次通过hash值,定位到Segment中的对应位置
【put方法】
- 为输入的key值,进行hash运算得到hash值
- 通过hash值定位到对应的Segment
- 为当前Segment加可重入锁
- 进入Segment找到对应位置,进行插入或者覆盖
- 释放可重入锁
【size方法】
在ConcurrentHashmap的size方法中采用了乐观锁与悲观锁相结合的方式。
正常来说,我们统计size时,直接获取各个Segment的子元素的个数之和即可。
当若是当我们刚刚计算结束某一Segment的元素之后,该Segment又进行了插入或者移除操作怎么办呢?
具体实现流程如下:
- 获取当前ConcurrentHashmap的修改次数version
- 遍历所有Sgement,获得子元素数量之和
- 再次获取当前ConcurrentHashmap的修改次数version
- 对比两次version,如果第二次大于第一次,说明统计过程中有修改,重新统计,尝试次数+1;若二者相同,说明没有修改,统计结束。
- 如果尝试次数超过阈值,则对每一个Segment加锁,再重新统计。
- 再次判断所有Segment的总修改次数是否大于上一次的总修改次数。由于已经加锁,次数一定和上次相等。
- 释放锁,统计结束
学习参考来源:小灰大神
【补充内容】
乐观锁与悲观锁的相关内容,在下一篇博客会更新