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()到目前为止已经顺利插入的部分数据。

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

相关文章
|
3月前
|
存储 安全 Java
Java HashMap 全面解析:原理、用法与实战要点
本文深入解析Java中HashMap的底层原理与使用实践,涵盖其“数组+链表+红黑树”的结构演变、哈希计算、扩容机制及线程安全问题,详解常用方法、性能优化与最佳实践,助力开发者高效掌握这一核心数据结构。
834 11
|
存储 缓存 安全
ConcurrentHashMap:使用方法和底层原理详解
ConcurrentHashMap:使用方法和底层原理详解
699 1
|
存储 Java
ArrayList自动扩容(详细篇)
ArrayList自动扩容(详细篇)
849 1
|
4月前
|
算法 Java 开发者
Java 中 HashMap 的底层实现原理详解
深入分析 Java HashMap 的底层实现原理,包括数据结构、hash 算法和扩容机制
Java 中 HashMap 的底层实现原理详解
|
存储 安全 Java
最爱问的高频ConcurrentHashMap原理,你会了吗?
ConcurrentHashMap 是 Java 中的线程安全散列表实现,允许多个线程同时访问和修改数据。它在 JDK 1.7 中通过分段锁机制将 HashMap 分为多个段,每个段使用独立的锁来保证线程安全;而在 JDK 1.8 中则采用 CAS 和 synchronized 结合的方式,提高了并发性能。与 HashMap 相比,ConcurrentHashMap 是线程安全的,支持更高的并发性能,且不支持 null 键和值。CAS(Compare-and-Swap)是一种无锁原子操作,用于确保多线程环境下的数据一致性,避免竞态条件。
678 5
|
存储 缓存 监控
【Java面试题汇总】JVM篇(2023版)
JVM内存模型、双亲委派模型、类加载机制、内存溢出、垃圾回收机制、内存泄漏、垃圾回收流程、垃圾回收器、G1、CMS、JVM调优
【Java面试题汇总】JVM篇(2023版)
|
Java
【JVM】双亲委派机制、打破双亲委派机制
【JVM】双亲委派机制、打破双亲委派机制
338 1
|
算法 安全 Java
Java并发基石ReentrantLock:深入解读其原理与实现
Java并发基石ReentrantLock:深入解读其原理与实现
|
存储 缓存 算法
图解LinkedHashMap原理
图解LinkedHashMap原理
385 0
|
存储 缓存 安全
ConcurrentHashMap底层实现原理
ConcurrentHashMap底层实现原理
ConcurrentHashMap底层实现原理