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

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

相关文章
|
小程序 前端开发 程序员
不得不说,这19个程序员兼职平台让我1年收入60w
关于程序员接私活,社会各界说法不一。
2081 1
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
1201 4
|
安全 Java
【Java面试】ConcurrentHashMap的key为什么不允许为null?
【Java面试】ConcurrentHashMap的key为什么不允许为null?
586 0
|
消息中间件 监控 Java
RocketMQ 同步发送、异步发送和单向发送,如何选择?
本文详细分析了 RocketMQ 中同步发送、异步发送和单向发送三种消息发送方式的原理、优缺点及适用场景。同步发送可靠性高但延迟较大,适合订单系统等场景;异步发送非阻塞且延迟低,适用于实时数据处理等场景;单向发送高效但可靠性低,适用于日志收集等场景。文章还提供了示例代码和核心源码分析,帮助读者更好地理解每种发送方式的特点。
2012 4
|
存储 网络协议 安全
阿里云服务器2核8G、4核16G、8核32G选型参考:经济型、通用算力型和通用型选择参考
2核8G、4核16G、8核32G配置是用户关注度比较高的热门配置,在阿里云服务器的实例规格中,这些配置一般有经济型e、通用算力型u1、通用型g7和通用型g8y等多种实例规格,虽然配置相同,但是这些实例规格之间的性能和价格差别是很大的,因此,我们有必要弄清楚他们之间的差别,这样才能根据自己的需求选择最适合自己的实例。本文将为您详细解析这些实例规格的性能、价格及应用场景,以供参考和选择。
阿里云服务器2核8G、4核16G、8核32G选型参考:经济型、通用算力型和通用型选择参考
|
存储 安全 Java
synchronized原理详解(通俗易懂超级好)
当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cpu。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。
synchronized原理详解(通俗易懂超级好)
|
10月前
|
人工智能 IDE 程序员
从 AI Coding 演进路径看通义灵码 AI 程序员的发布,让更多 idea 变成产品
通义灵码 2.0 不仅正式发布 AI 程序员,还升级了很多基础能力,使用场景多样。繁星计划的推出更为大学生提供了免费的智能编码助手,助力科技创新。让不具备编码能力的人也可以将 idea 变成产品,帮助到更多开发者和泛开发者。
|
前端开发 小程序 Java
【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅
本文详细介绍了如何在SpringBoot项目中统一处理接口返回结果及全局异常。首先,通过封装`ResponseResult`类,实现了接口返回结果的规范化,包括状态码、状态信息、返回信息和数据等字段,提供了多种成功和失败的返回方法。其次,利用`@RestControllerAdvice`和`@ExceptionHandler`注解配置全局异常处理,捕获并友好地处理各种异常信息。
6632 0
【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅
|
存储 监控 数据库
什么是聚集索引和非聚集索引?
【8月更文挑战第3天】
8331 6
|
机器学习/深度学习 编解码 算法
超分辨率相关的开源项目
该文档介绍了多种超分辨率模型及其GitHub项目地址,包括Real-ESRGAN(优化真实图片质量)、RCAN(基于残差结构与通道注意力机制)、SwinIR(基于Swin Transformer的图像恢复)、FSRCNN(轻量级快速超分辨率)、EDSR(增强型深度残差网络)、SRGAN(利用GAN的超分辨率模型)及LapSRN(多级Laplacian金字塔超分辨率)。