文章摘要
GPT 4
此内容根据文章生成,仅用于文章内容的解释与总结
投诉

一、JDK 1.7

1.1 加锁机制

ConcurrentHashMap 在 JDK 1.7 中,提供了一种粒度更细的加锁机制,这种机制叫分段锁「Lock Striping」。整个哈希表被分为多个段,每个段都独立锁定。读取操作不需要锁,写入操作仅锁定相关的段。这减小了锁冲突的几率,从而提高了并发性能。

这种机制的优点:在并发环境下将实现更高的吞吐量,而在单线程环境下只损失非常小的性能。

可以这样理解分段锁,就是将数据分段,对每一段数据分配一把锁。当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

有些方法需要跨段,比如 size()isEmpty()containsValue(),它们可能需要锁定整个表而不仅仅是某个段,这需要按顺序锁定所有段,操作完后,再按顺序释放所有段的锁。如下图:

jdk1.7之前的构成图

1.2 jdk1.7之前的结构

ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组+链表构成的。Segment 是一种可重入的锁 ReentrantLock,HashEntry 则用于存储键值对数据。

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

ConcurrentHashMap使用链表来存储这些具有相同索引值的键值对。具体来说,每个数组位置(在JDK 1.7中实际上是Segment内部的数组,而在JDK 1.8及以后是Node数组)存储的是链表的头节点。如果发生哈希冲突,新的键值对会被添加到这个链表的末尾。

接下来看看Segment的结构组成:

单一的 Segment 结构如下:

像这样的 Segment 对象,在 ConcurrentHashMap 集合中有多少个呢?有 2 的 N 次方个,共同保存在一个名为 segments 的数组当中。 因此整个 ConcurrentHashMap 的结构如下:

可以说,ConcurrentHashMap 是一个二级哈希表。在一个总的哈希表下面,有若干个子哈希表。

Case1:不同 Segment 的并发写入(可以并发执行)

Case2:同一 Segment 的一写一读(可以并发执行)

Case3:同一 Segment 的并发写入

二、JDK1.8往后

而在 JDK 1.8 中,ConcurrentHashMap 主要做了两个优化:

  • HashMap 一样,链表也会在长度达到 8 的时候转化为红黑树,这样可以提升大量冲突时候的查询效率;
  • 以某个位置的头结点(链表的头结点或红黑树的 root 结点)为锁,**配合自旋+CAS(乐观锁)**避免不必要的锁开销,进一步提升并发性能。

相比 JDK1.7 中的 ConcurrentHashMap,JDK1.8 中的 ConcurrentHashMap 取消了 Segment 分段锁,采用 CAS + synchronized 来保证并发安全性,整个容器只分为一个 Segment,即 table 数组。

JDK1.8 中的 ConcurrentHashMap 对节点 Node 类中的共享变量,和 JDK1.7 一样,使用 volatile 关键字,保证多线程操作时,变量的可见性。

HashMap与ConcurrentHashMap工作原理、区别和总结_hashmap和concurrentmap实现原理区别-CSDN博客