news 2026/3/29 9:44:35

深入浅出ConCurrentHashMap(一)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入浅出ConCurrentHashMap(一)
ConcurrentHashMap 是 Java 并发包 (java.util.concurrent) 中的一种线程安全的哈希表实现。
HashMap 在多线程环境下扩容会出现 CPU 接近 100% 的情况,因为 HashMap 并不是线程安全的,我们可以通过 Collections 的Map synchronizedMap(Map m)将 HashMap 包装成一个线程安全的 map。比如 SynchronzedMap 的 put 方法源码就是加锁过的:
public V put(K key, V value) { synchronized (mutex) {return m.put(key, value);} }
不过,这并不是最优雅的方式。相对于 HashMap,ConcurrentHashMap 就是线程安全的 map,其中利用了锁分段的思想大大提高了并发的效率1.8 版本舍弃了 segment,并且使用了大量的 synchronized,以及 CAS 无锁操作以保证 ConcurrentHashMap 的线程安全性。为什么不用 ReentrantLock 而是 synchronzied 呢?实际上,synchronzied 做了很多的优化,这个我们前面也讲过了,包括偏向锁、轻量级锁、重量级锁,可以依次向上升级锁状态,因此,synchronized 相较于 ReentrantLock 的性能其实差不多,甚至在某些情况更优。

ConcurrentHashMap 的变化

ConcurrentHashMap 在 JDK 1.7 和 JDK 1.8 中有一些区别。

JDK 1.7

ConcurrentHashMap 在 JDK 1.7 中,提供了一种粒度更细的加锁机制,这种机制叫分段锁「Lock Striping」。整个哈希表被分为多个段,每个段都独立锁定。读取操作不需要锁,写入操作仅锁定相关的段。这减小了锁冲突的几率,从而提高了并发性能。
这种机制的优点:在并发环境下将实现更高的吞吐量,而在单线程环境下只损失非常小的性能。
可以这样理解分段锁,就是将数据分段,对每一段数据分配一把锁。当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。有些方法需要跨段,比如size()isEmpty()containsValue(),它们可能需要锁定整个表而不仅仅是某个段,这需要按顺序锁定所有段,操作完后,再按顺序释放所有段的锁。如下图:
ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组构成的。Segment 是一种可重入的锁 ReentrantLock,HashEntry 则用于存储键值对数据。一个 ConcurrentHashMap 里包含一个 Segment 数组,Segment 的结构和 HashMap 类似,是一种数组和链表结构,一个 Segment 里包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得它对应的 Segment 锁。
单一的 Segment 结构如下:
像这样的 Segment 对象,在 ConcurrentHashMap 集合中有 2 的 N 次方个,共同保存在一个名为 segments 的数组当中。 因此整个 ConcurrentHashMap 的结构如下:
可以说,ConcurrentHashMap 是一个二级哈希表。在一个总的哈希表下面,有若干个子哈希表。
Case1:不同 Segment 的并发写入(可以并发执行)
Case2:同一 Segment 的一写一读(可以并发执行)
Case3:同一 Segment 的并发写入
Segment 的写入是需要上锁的,因此对同一 Segment 的并发写入会被阻塞。
由此可见,ConcurrentHashMap 中每个 Segment 各自持有一把锁。在保证线程安全的同时降低了锁的粒度,让并发操作效率更高。
ConcurrentHashMap 读写过程如下:
get 方法
  • 为输入的 Key 做 Hash 运算,得到 hash 值。
  • 通过 hash 值,定位到对应的 Segment 对象
  • 再次通过 hash 值,定位到 Segment 当中数组的具体位置。
put 方法
  • 为输入的 Key 做 Hash 运算,得到 hash 值。
  • 通过 hash 值,定位到对应的 Segment 对象
  • 获取可重入锁
  • 再次通过 hash 值,定位到 Segment 当中数组的具体位置。
  • 插入或覆盖 HashEntry 对象。
  • 释放锁。

JDK 1.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 关键字,保证多线程操作时,变量的可见性!
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; volatile V val; volatile Node<K,V> next; Node(int hash, K key, V val, Node<K,V> next) { this.hash = hash; this.key = key; this.val = val; this.next = next; } ...... }

ConcurrentHashMap 的字段

1、tablevolatile Node[] table:
装载 Node 的数组,作为 ConcurrentHashMap 的底层容器,采用懒加载的方式,直到第一次插入数据的时候才会进行初始化操作,数组的大小总是为 2 的幂次方。
2、nextTablevolatile Node[] nextTable
扩容时使用,平时为 null,只有在扩容的时候才为非 null
3、sizeCtlvolatile int sizeCtl
该属性用来控制 table 数组的大小,根据是否初始化和是否正在扩容有几种情况:
  • 当值为负数时:如果为-1 表示正在初始化,如果为 -N 则表示当前正有 N-1 个线程进行扩容操作;
  • 当值为正数时:如果当前数组为 null 的话表示 table 在初始化过程中,sizeCtl 表示为需要新建数组的长度;若已经初始化了,表示当前数据容器(table 数组)可用容量,也可以理解成临界值(插入节点数超过了该临界值就需要扩容),具体指为数组的长度 n 乘以 加载因子 loadFactor;
  • 当值为 0 时,即数组长度为默认初始值。
4、sun.misc.Unsafe U
在 ConcurrentHashMap 的实现中,可以看到用了大量的U.compareAndSwapXXXX方法去修改 ConcurrentHashMap 的一些属性。
这些方法实际上是利用了 CAS 算法用于保证线程安全性,这是一种乐观策略:假设每一次操作都不会产生冲突,当且仅当冲突发生的时候再去尝试。
我们前面也讲过了,CAS 操作依赖于现代处理器指令集,通过底层的CMPXCHG指令实现。CAS(V,O,N)核心思想为:若当前变量实际值 V 与期望的旧值 O 相同,则表明该变量没被其他线程进行修改,因此可以安全的将新值 N 赋值给变量;若当前变量实际值 V 与期望的旧值 O 不相同,则表明该变量已经被其他线程做了处理,此时将新值 N 赋给变量操作就是不安全的,在进行重试
在并发容器中,CAS 是通过sun.misc.Unsafe类实现的,该类提供了一些可以直接操控内存和线程的底层操作,可以理解为 Java 中的“指针”。该成员变量的获取是在静态代码块中:
static { try { U = sun.misc.Unsafe.getUnsafe(); ....... } catch (Exception e) { throw new Error(e); } }

以上是第一部分的主要内容。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/28 0:17:20

高压纹波加热电源硬核解析!EA-RW600 赋能汽车高压部件检测

在新能源汽车、电力电子等领域的高压器件研发与检测中&#xff0c;纹波加热测试是评估器件耐纹波能力、热稳定性和长期可靠性的关键环节。纹波电流通过器件时产生的焦耳热&#xff0c;会直接影响器件的工作寿命和安全性能&#xff0c;这就要求测试设备能精准模拟真实工况下的纹…

作者头像 李华
网站建设 2026/3/15 18:55:21

细胞多尺度仿真软件:CellSys_(2).CellSys软件安装与配置

CellSys软件安装与配置 1. 软件安装 1.1 下载CellSys软件 首先&#xff0c;您需要从CellSys官方网站或指定的下载渠道获取软件的安装包。官方网站通常会提供最新版本的下载链接&#xff0c;以及不同操作系统的安装包。以下是下载步骤&#xff1a; 访问官方网站&#xff1a;打…

作者头像 李华
网站建设 2026/3/17 10:07:51

细胞多尺度仿真软件:CellSys_(5).细胞动力学与运动行为模拟

细胞动力学与运动行为模拟 在细胞多尺度仿真软件中&#xff0c;细胞的动力学与运动行为模拟是至关重要的模块之一。这一部分主要涉及细胞在不同物理和化学环境下的运动行为&#xff0c;以及细胞内分子和细胞器的动态变化。通过模拟这些行为&#xff0c;研究人员可以更好地理解…

作者头像 李华
网站建设 2026/3/26 14:21:22

第十五课 · 实战篇:缓存三大灾难落地防御(穿透/击穿/雪崩)

第14课我们学会“怎么用缓存”&#xff0c;但真正线上要命的是&#xff1a;缓存一出问题&#xff0c;数据库会不会被打爆&#xff1f;这篇用最小实战把三大灾难的工程解法写到代码层面&#xff1a; 穿透 → 击穿 → 雪崩&#xff0c;每个都给你一段“可复用模板”。0. 实战目标…

作者头像 李华