ConcurrentHashMap底层实现原理

简介: ConcurrentHashMap底层实现原理

接着上一篇文章HashMap,咱们讲到HashMap是线程不安全的,线程安全的Map不是没有,比如HashTable等,但是他们都有个致命的问题,性能,看下图应该能明白:

他们都是对整个map进行加锁操作的,可以看到只有A操作完,B才能操作,效率可以说很低了,怎么办呢,我们可以采用分段加锁的方式来实现,同样的1.7和1.8版本的jdk里ConcurrentHashMap实现是不一样的,咱们先看看1.7版本的。

1.7版本首先我们要理解一个概念,Segment,用小编的话讲,这个东西基本就是一个原来的HashMapConcurrentHashMap就是由多个这样的HashMap组成的,最后形成了嵌套的关系,结构如图:

ConcurrentHashMap从原来数组直接挂entry,变成了数组下面挂的是一个一个的SegmentSegment里面是一个数组,数组下面才是entry,就是一个嵌套而已。像这样的Segment对象,在ConcurrentHashMap集合中有多少个呢?有2的N次方个(数组的长度和HashMap扩容机制有关),共同保存在一个名为segments的数组当中。

ConcurrentHashMap在实际读写的时候,相同的Segment是可以同时进行读写的,但是同时写入不行,需要加锁,不同的Segment的写入是可以一起执行的,显而易见吧,和HashTable对整个map加锁相比,锁的粒度降低了不少吧,效率高就高在这里呢


Get方法(不涉及到锁相关的操作,两次hash操作):


1.为输入的Key做Hash运算,得到hash值。

2.通过hash值,定位到对应的Segment对象

3.再次通过hash值,定位到Segment当中数组的具体位置。


Put方法(写的时候要先获取到锁才能写,但是锁的粒度在Segment上):


1.为输入的Key做Hash运算,得到hash值。

2.通过hash值,定位到对应的Segment对象

3.获取可重入锁

4.再次通过hash值,定位到Segment当中数组的具体位置。

5.插入或覆盖HashEntry对象。

6.释放锁。


   细心的小伙伴可能有疑问了,这都嵌套一层了,那统计这个ConcurrentHashMap的数量size的时候咋统计,肯定是获取到所有的entry才对吧,是的没错,但是获取的方法没有那么简单,并发就涉及到可能刚统计完这个map里有100个元素,同时第101个元素就插入或者删除了,这里就很巧妙了。ConcurrentHashMap在这个size函数里用到了乐观锁和悲观锁两种思想。


首先用乐观锁的思想,记录遍历之前所有Segment的总修改次数,然后开始遍历,如果遍历完之后,所有Segment的总修改次数等于遍历之前的修改次数,说明遍历期间没有发生数据的变化,本次统计的entry总数就是size的大小,返回即可,如果发生变化了就重新统计,重复上面的步骤,如果多次(有个阈值)都发生了变化导致统计失败,就转成悲观锁思想,把全部Segment都加锁,统计完entry数量后再释放。


在1.8中,ConcurrentHashMap直接舍弃了Segment的做法,因为他数据结构太复杂了,原来是对需要进行数据操作的Segment加锁,现调整为对每个数组元素加锁(Node),核心思想采用了synchronized+CAS+数组+链表+红黑树来实现,整体的原理图如下:

为啥是数组+链表+红黑树,我就不过多说明了哈,不明白的朋友去看小编的上一篇文章HashMap的底层实现原理有介绍的,synchronized是只对数组的第一个节点加锁,CAS是乐观锁的意思在数组节点是空的时候用乐观锁放node节点进去。  ConcurrentHashMap获取数据时就很简单函数根据key的hash值来计算在哪个桶中,再遍历桶,查找元素,若找到则返回该结点,否则,返回null。增加一个元素比较复杂,具体流程图如下:



① 判断存储的key、value是否为空,若为空,则抛出异常,否则,进入步骤②

② 计算key的hash值,随后进入无限循环,该无限循环可以确保成功插入数据,若table表为空或者长度为0,则初始化table表,否则,进入步骤③

③ 根据key的hash值取出table表中的结点元素,若取出的结点为空(该桶为空),则使用CAS将key、value、hash值生成的结点放入桶中。否则,进入步骤④

④ 若该结点的的hash值为MOVED,则对该桶中的结点进行扩容,否则,进入步骤⑤

⑤ 对桶中的第一个结点(即table表中的结点)进行synchronized加锁,对该桶进行遍历,桶中的结点的hash值与key值与给定的hash值和key值相等,

则根据标识选择是否进行更新操作(用给定的value值替换该结点的value值),若遍历完仍没有找到hash值与key值和指定的hash值与key值相等的结点,

则直接新生一个结点并赋值为之前最后一个结点的下一个结点。进入步骤⑥

⑥ 若binCount值达到红黑树转化的阈值,则将桶中的结构转化为红黑树存储,最后,增加binCount的值。


ConcurrentHashMap和HashTable的虽然都是线程安全,却实现差异巨大,HashTable的任何操作都会把整个表锁住,是阻塞的。好处是:总能获取最实时的更新,比如说线程A调用putAll()写入大量数据,期间线程B调用get(),线程B就会被阻塞,直到线程A完成putAll(),因此线程B肯定能获取到线程A写入的完整数据。坏处是所有调用都需要排队,效率较低。ConcurrentHashMap是设计为非阻塞的。在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。坏处是:严格来说,读取操作不能保证反映最近的更新。例如线程A调用putAll()写入大量数据,期间线程B调用get(),则只能get()到目前为止已经顺利插入的部分数据。

最后,大家想了解更多知识的,可以继续关注公众号,不定时推送。分享了这么牛逼的知识,还不请小编喝个水吗,哈哈哈,欢迎土豪直接赏赞,谢谢,您的支持就是小编最大的动力。

相关文章
|
22天前
|
存储 缓存 安全
面试题-HashMap底层原理与HashTable的区别
字节跳动面试题-HashMap底层原理与HashTable的区别
28 0
|
8月前
|
存储 算法 安全
HashMap底层实现原理
HashMap底层实现原理
101 0
|
4月前
|
存储 算法
HashMap的实现原理
HashMap的实现原理
|
4月前
|
存储 安全
ConcurrentHashMap 底层具体实现
ConcurrentHashMap 底层具体实现
|
6月前
|
存储 算法 Java
从HashMap的执行流程开始 揭开HashMap底层实现
从HashMap的执行流程开始 揭开HashMap底层实现
20 0
|
10月前
|
存储 安全 Java
ConcurrentHashMap概念与深入理解
ConcurrentHashMap是Java集合框架中的一个重要类,它是线程安全的哈希表实现。相比于普通的HashMap,ConcurrentHashMap在多线程环境中提供了更好的性能和可靠性。本文将详细介绍ConcurrentHashMap的概念、特点以及其内部实现原理。
125 0
|
12月前
|
存储 Java Serverless
HashMap底层原理
1. 基本概念 2. HashMap 的底层数据结构 3. HashMap 的 put 方法流程 4. 怎么计算节点存储的下标 5. Hash 冲突 1)概念 2)解决 hash 冲突的办法 开放地址法 再哈希法 链地址法 建立公共溢出区 6. HashMap 的扩容机制 1)扩容时涉及到的几个属性 2)扩容的条件 3)扩容的简要流程
62 0
|
存储 索引
30. 说一下HashMap的实现原理?中
30. 说一下HashMap的实现原理?中
67 0
30. 说一下HashMap的实现原理?中
|
存储 缓存 Java
30. 说一下HashMap的实现原理?上
30. 说一下HashMap的实现原理?上
96 0
30. 说一下HashMap的实现原理?上
|
索引
30. 说一下HashMap的实现原理?下
30. 说一下HashMap的实现原理?下
79 0