HashMap和ConcurrentHashMap底层原理

本文详细比较了JDK1.8前后HashMap的底层实现,重点讲述了JDK1.8中ConcurrentHashMap的数据结构升级,从链表到红黑树的转换,以及如何通过分段锁和CAS+Synchronized技术提高线程安全性,以降低锁竞争和并发性能提升。
摘要由CSDN通过智能技术生成

HashMap的数据结构

JDK1.8 之前

JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列

HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。

所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。

JDK1.8 之后

相比于之前的版本,JDK1.8 以后在解决哈希冲突时有了较大的变化。

当链表长度大于阈值(默认为 8)时,会首先调用 treeifyBin()方法。这个方法会根据 HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是执行 resize() 方法对数组扩容。

ConcurrentHashMap底层数据结构

ConcurrentHashMap 是一种线程安全的高效 Map 集合, jdk1.7 1.8 也做了很
多调整。
JDK1.7 的底层采用是 分段的数组 + 链表 实现
JDK1.8 采用的数据结构跟 HashMap1.8 的结构一样,数组 + 链表 / 红黑二叉树

JDK1.7

jdk1.7 ConcurrentHashMap 里包含一个 Segment 数组。 Segment 的结构
HashMap 类似,是一 种数组和链表结构,一个 Segment 包含一个
HashEntry 数组,每个 HashEntry 是一个链表结构 的元素,每个 Segment
护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修 改
时,必须首先获得对应的 Segment 的锁。
Segment 是一种可重入的锁 ReentrantLock ,每个 Segment 守护一个
HashEntry 数组里得元 素,当对 HashEntry 数组的数据进行修改时,必须首
先获得对应的 Segment

JDK1.8

jdk1.8 中的 ConcurrentHashMap 做了较大的优化,性能提升了不少。首先是它的数据结构与jdk1.8 hashMap 数据结构完全一致。其次是放弃了 Segment臃肿的设计,取而代之的是采用 Node + CAS + Synchronized 来保证并发安全进行实现,synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要hash 不冲 突,就不会产生并发 , 效率得到提升

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

在JDK 1.7中,ConcurrentHashMap使用了分段锁技术,即将哈希表分成多个段,每个段拥有一个独立的锁这样可以在多个线程同时访问哈希表时,只需要锁住需要操作的那个段,而不是整个哈希表,从而提高了并发性能。
虽然JDK 1.7的这种方式可以减少锁竞争,但是在高并发场景下,仍然会出现锁竞争,从而导致性能下降。

在JDK 1.8中,ConcurrentHashMap的实现方式进行了改进,使用分段锁(思想)和“CAS+Synchronized的机制来保证线程安全。在JDK1.8中,ConcurrentHashMap会在添加元素时,如果某个段为空,那么使用CAS操作来添加新节点;如果段不为空,使用Synchronized锁住当前段,再次尝试put。这样可以避免分段锁机制下的锁粒度太大,以及在高并发场景下,由于线程数量过多导致的锁竞争问题,提高了并发性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值