ConcurrentHashMap是如何保证线程安全的?

简介: ConcurrentHashMap相当于是HashMap的多线程版本,它的功能本质上和HashMap没什么区别。因为HashMap在并发操作的时候会出现各种问题,比如死循环问题、数据覆盖等问题。而这些问题,只要使用ConcurrentHashMap就可以完美地解决。那问题来到了,ConcurrentHashMap它是如何保证线程安全的呢?

ConcurrentHashMap相当于是HashMap的多线程版本,它的功能本质上和HashMap没什么区别。因为HashMap在并发操作的时候会出现各种问题,比如死循环问题、数据覆盖等问题。而这些问题,只要使用ConcurrentHashMap就可以完美地解决。那问题来到了,ConcurrentHashMap它是如何保证线程安全的呢?


1、JDK1.7实现原理


首先,我们来看JDK 1.7中ConcurrentHashMap的底层结构,它基本延续了HashMap的设计,采用的是数组 加 链表的形式。和HashMap不同的是,ConcurrentHashMap中的数组设计   分为大数组Segment和小数组HashEntry,来着这张图。

c09db60bf090c2adb14470be19b9f24b.png

大树组Segment可以理解为一个数据库,而每个数据库(Segment)中又有很多张表(HashEntry),每个HashEntry中又有很多条数据,这些数据是用链表连接的。了解了ConcurrentHashMap的基本结构设计,我们再来看它的线程安全实现,就比较简单了。


接下来我们来对照JDK1.7中ConcurrentHashMap的put()方法源码实现。

47747fd87a76c793d56ccc0741ce2c58.png

因为Segment本身是基于ReentrantLock重入锁实现的加锁和释放锁的操作,这样就能保证多个线程同时访问ConcurrentHashMap时,同一时间只能有一个线程能够操作相应的节点,这样就保证了ConcurrentHashMap的线程安全。

fa34f544e7c795470c18425d05311ed5.png

也就是说ConcurrentHashMap的线程安全是建立在Segment加锁的基础上的,所以,我们称它为分段锁或者片段锁,如图中所示。


那JDK1.8又是如何实现的呢?


2、JDK1.8优化内容


在JDK1.7中,ConcurrentHashMap虽然是线程安全的,但因为它的底层实现是数组加链表的形式,所以在数据比较多情况下,因为要遍历整个链表,会降低访问性能。所以,JDK1.8以后采用了数组 加  链表 加 红黑树的方式优化了ConcurrentHashMap的实现,具体实现如图所示。

7a890c55b8808602ec5fef7e3589962b.png

当链表长度大于8,并且数组长度大于64时,链表就会升级为红黑树的结构。JDK 1.8中的ConcurrentHashMap虽然保留了Segment的定义,但这,仅仅是为了保证序列化时的兼容性,不再有任何结构上的用处了。


那在JDK 1.8中ConcurrentHashMap的源码是如何实现的呢?它主要是使用了CAS 加 volatile 或者 synchronized 的方式来保证线程安全。

f947d0608a1a8928593bccec7acf7693.png

我们可以从源码片段中看到,添加元素时首先会判断容器是否为空,


如果为空则使用    volatile    加   CAS    来初始化,


如果容器不为空,则根据存储的元素计算该位置是否为空。


如果根据存储的元素计算结果为空则利用   CAS   设置该节点;


如果根据存储的元素计算为空不为空,则使用  synchronized   ,然后,遍历桶中的数据,并替换或新增节点到桶中,


最后再判断是否需要转为红黑树。这样就能保证并发访问时的线程安全了。


如果把上面的执行用一句话归纳的话,就相当于是ConcurrentHashMap通过对头结点加锁来保证线程安全的。

266653dc38fefde4ef9f021cf164330f.png

这样设计的好处是,使得锁的粒度相比Segment来说更小了,发生hash冲突 和  加锁的频率也降低了,在并发场景下的操作性能也提高了。而且,当数据量比较大的时候,查询性能也得到了很大的提升。


2、总结


最后,我们来总结一下:

5cd3277ffc2be1386b8e0764b00d363a.png

1、ConcurrentHashMap在JDK 1.7中使用的数组 加 链表的结构,其中数组分为两类,大树组Segment 和 小数组 HashEntry,而加锁是通过给Segment添加ReentrantLock重入锁来保证线程安全的。


2、ConcurrentHashMap在JDK1.8中使用的是数组 加 链表 加 红黑树的方式实现,它是通过 CAS 或者 synchronized  来保证线程安全的,并且缩小了锁的粒度,查询性能也更高。


ConcurrentHashMap中有很多设计思想是值得我们去学习和借鉴的,比如说锁的粒度控制、分段锁的设计等等,都可以应用在实际的业务开发场景中。我们通过学习这些底层原理从中获取很多的设计思路,帮助我们更高效地去解决实际问题。


好了,关于ConcurrentHashMap的分享就讲到这里,听懂的小伙伴,请关注点个赞,下次不迷路。


S信【Tom】或【666】即可免费领取需要更多干货内容,还有海量面试资料,只弹干货不惨水!


本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!

如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。

相关文章
|
3月前
|
存储 安全 Java
我们来聊聊线程安全吧
我们来聊聊线程安全吧
|
8月前
|
安全 Java 编译器
深入理解线程安全
在多线程编程中,线程安全是一个至关重要的概念。线程安全可能到导致数据不一致,应用程序崩溃和其他不可预测的后果。本文将深入探讨线程安全问题的根本原因,并通过Java代码示例演示如何解决这些问题。
86 0
|
9月前
|
安全 Java 编译器
关于线程安全问题
关于线程安全问题
74 0
|
9月前
|
存储 安全
什么时候考虑线程安全?
什么时候考虑线程安全?
64 0
|
10月前
|
缓存 安全 Java
什么是线程安全?如何保证线程安全?Java 中保证线程安全的方法有哪些?【重要】
什么是线程安全?如何保证线程安全?Java 中保证线程安全的方法有哪些?【重要】
673 0
|
安全
集合类在并发情况下如何保证线程安全
在正常单线程的情况下不会出现问题,当多线程的时候,List会出现 java.util.ConcurrentModificationException 这种异常
66 0
|
缓存 安全 Java
67. 谈谈ConcurrentHashMap是如何保证线程安全的?
67. 谈谈ConcurrentHashMap是如何保证线程安全的?
124 0
67. 谈谈ConcurrentHashMap是如何保证线程安全的?
|
安全 Java 大数据
CopyOnWriteArrayList 是如何保证线程安全的?
在上一篇文章里,我们聊到了ArrayList 的线程安全问题,其中提到了 CopyOnWriteArrayList 的解决方法。那么 CopyOnWriteArrayList 是如何解决线程安全问题的,背后的设计思想是什么,今天我们就围绕这些问题展开。
85 0
|
存储 开发框架 算法
线程安全
线程安全
69 0
|
存储 安全 程序员
你管这叫"线程安全"?
今日份的干粮: 1.什么叫线程安全? 2.线程安全与变量的关系? •变量又与堆/栈/静态存储区有密切关系
你管这叫"线程安全"?