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

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

相关文章
|
2月前
|
存储 安全 Java
Java HashMap 全面解析:原理、用法与实战要点
本文深入解析Java中HashMap的底层原理与使用实践,涵盖其“数组+链表+红黑树”的结构演变、哈希计算、扩容机制及线程安全问题,详解常用方法、性能优化与最佳实践,助力开发者高效掌握这一核心数据结构。
356 10
|
消息中间件 监控 Java
RocketMQ 同步发送、异步发送和单向发送,如何选择?
本文详细分析了 RocketMQ 中同步发送、异步发送和单向发送三种消息发送方式的原理、优缺点及适用场景。同步发送可靠性高但延迟较大,适合订单系统等场景;异步发送非阻塞且延迟低,适用于实时数据处理等场景;单向发送高效但可靠性低,适用于日志收集等场景。文章还提供了示例代码和核心源码分析,帮助读者更好地理解每种发送方式的特点。
2320 4
|
存储 缓存 监控
【Java面试题汇总】JVM篇(2023版)
JVM内存模型、双亲委派模型、类加载机制、内存溢出、垃圾回收机制、内存泄漏、垃圾回收流程、垃圾回收器、G1、CMS、JVM调优
【Java面试题汇总】JVM篇(2023版)
|
缓存 监控 安全
Spring AOP 详细深入讲解+代码示例
Spring AOP(Aspect-Oriented Programming)是Spring框架提供的一种面向切面编程的技术。它通过将横切关注点(例如日志记录、事务管理、安全性检查等)从主业务逻辑代码中分离出来,以模块化的方式实现对这些关注点的管理和重用。 在Spring AOP中,切面(Aspect)是一个模块化的关注点,它可以跨越多个对象,例如日志记录、事务管理等。切面通过定义切点(Pointcut)和增强(Advice)来介入目标对象的方法执行过程。 切点是一个表达式,用于匹配目标对象的一组方法,在这些方法执行时切面会被触发。增强则定义了切面在目标对象方法执行前、执行后或抛出异常时所
17627 4
|
机器学习/深度学习 编解码 算法
超分辨率相关的开源项目
该文档介绍了多种超分辨率模型及其GitHub项目地址,包括Real-ESRGAN(优化真实图片质量)、RCAN(基于残差结构与通道注意力机制)、SwinIR(基于Swin Transformer的图像恢复)、FSRCNN(轻量级快速超分辨率)、EDSR(增强型深度残差网络)、SRGAN(利用GAN的超分辨率模型)及LapSRN(多级Laplacian金字塔超分辨率)。
|
存储 监控 数据库
什么是聚集索引和非聚集索引?
【8月更文挑战第3天】
9069 6
|
Java
【JVM】双亲委派机制、打破双亲委派机制
【JVM】双亲委派机制、打破双亲委派机制
298 1
|
编解码 安全 算法
Java多线程基础-18:线程安全的集合类与ConcurrentHashMap
如果这些单线程中的集合类确实需要在多线程中使用,该怎么办呢?思路有两个: 最直接的方式:使用锁,手动保证。如多个线程修改ArrayList对象,此时就可能有问题,就可以给修改操作进行加锁。但手动加锁的方式并不是很方便,因此标准库还提供了一些线程安全的集合类。
766 4
|
消息中间件 存储 Java
消息队列-死信队列
消息队列-死信队列
644 0
|
负载均衡 算法 Nacos
SpringCloud之LoadBalancer自定义负载均衡算法,基于nacos权重
ReactorLoadBalancer接口,实现自定义负载算法需要实现该接口,并实现choose逻辑,选取对应的节点。
1737 0