什么是ConcurrentHashMap?实现原理是什么?

什么是ConcurrentHashMap?实现原理是什么?

在多线程环境下,使用HashMap进行put操作时存在丢失数据的情况,为了避免这种bug的隐患,强烈建议使用ConcurrentHashMap代替HashMap。

HashTable是一个线程安全的类,它使用synchronized来锁住整张Hash表来实现线程安全,即每次锁住整张表让线程独占,相当于所有线程进行读写时都去竞争一把锁,导致效率非常低下。

ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,允许多个修改操作并发进行,其关键在于使用了锁分段技术。它使用了多个锁来控制对hash表的不同部分进行的修改。对于JDK1.7版本的实现, ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的Hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。JDK1.8的实现降低锁的粒度,JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点)。

实现原理

JDK1.7

JDK1.7 中的 ConcurrentHashMap 是由 Segment 数组结构和链表数组 HashEntry 结构组成,即 ConcurrentHashMap 把哈希桶数组切分成多个段Segment ,每个Segment 有 n 个 链表数组(HashEntry)组成,也就是通过分段锁来实现的。

ConcurrentHashMap 定位一个元素的过程需要进行两次Hash操作,第一次 Hash 定位到 Segment,第二次 Hash 定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是 Hash 的过程要比普通的 HashMap 要长,但是带来的好处是写操作的时候可以只对元素所在的 Segment 进行操作即可,不会影响到其他的 Segment,这样,在最理想的情况下,ConcurrentHashMap 可以最高同时支持 Segment 数量大小的写操作(刚好这些写操作都非常平均地分布在所有的 Segment上),所以,通过这一种结构,ConcurrentHashMap 的并发能力可以大大的提高

如下图所示,首先将数据分为一段一段(Segment)的存储,然后给每一段(Segment)数据配一把锁,当一个线程占用锁访问其中一段数据时,其他段的数据也能被其他线程访问,实现了真正的并发访问。

Segment 是 ConcurrentHashMap 的一个内部类,主要的组成如下:

Segment 继承了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。Segment 默认为 16,也就是并发度为 16。

存放元素的 HashEntry,也是一个静态内部类,主要的组成如下:

其中,用 volatile 修饰了 HashEntry 的数据 value 和 下一个节点 next,保证了多线程环境下数据获取时的可见性!

为什么要用二次hash

主要原因是为了构造分离锁,使得对于map的修改不会锁住整个容器,提高并发能力。当然,没有一种东西是绝对完美的,二次hash带来的问题是整个hash的过程比hashmap单次hash要长,所以,如果不是并发情形,不要使concurrentHashmap。

JAVA7之前ConcurrentHashMap主要采用分段锁机制,在对某个Segment进行操作时,将该Segment锁定,不允许对其进行非查询操作,而在JAVA8之后采用CAS无锁算法,这种乐观操作在完成前进行判断,如果符合预期结果才给予执行,对并发操作提供良好的优化.

JDK1.8

在数据结构上, JDK1.8 中的ConcurrentHashMap 选择了与 HashMap 相同的Node数组+链表+红黑树结构;在锁的实现上,抛弃了原有的 Segment 分段锁,采用CAS + synchronized实现更加细粒度的锁。

将锁的级别控制在了更细粒度的哈希桶数组元素级别,也就是说只需要锁住这个链表头节点或红黑树的根节点,就不会影响其他的哈希桶数组元素的读写,大大提高了并发度。

ConcurrentHashMap中变量使用final和volatile修饰有什么用呢?

在 ConcurrentHashMap 中,使用 final 和 volatile 修饰变量可以提高线程安全性和可见性,具体作用如下: 

1. final:使用 final 修饰的变量表示不可变变量,即该变量的值在初始化之后不能被修改。在 ConcurrentHashMap 中,使用 final 修饰 Node 类中的 key 和 hash 变量,可以保证它们在初始化之后不会被修改,从而避免了多线程之间的竞争和冲突。final修饰变量可以保证变量不需要同步就可以被访问和共享

2. volatile:使用 volatile 修饰的变量表示该变量是可见的,即对该变量的修改会立即被其他线程所感知。在 ConcurrentHashMap 中,使用 volatile 修饰了 Segment 类中的 count 和 modCount 变量,可以保证它们的修改对其他线程是可见的,从而避免了多线程之间的冲突和数据不一致的问题。

需要注意的是,虽然使用 final 和 volatile 修饰变量可以提高线程安全性和可见性,但是并不能完全保证线程安全和正确性。在使用 ConcurrentHashMap 时,还需要注意其他方面的线程安全问题,例如迭代器的使用、复合操作的原子性等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

茶叶蛋xq

打赏有用的话还要工作干嘛

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值