news 2026/6/26 16:25:24

深入浅出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/6/26 3:47:02

wsl中改了 /etc/resolv.conf,一重启就没了?

问题&#xff1a;wsl中改了 /etc/resolv.conf&#xff0c;一重启就没了&#xff1f; 操作步骤 第 1 步&#xff1a;确认禁止 WSL 自动生成 DNS&#xff08;你可能已经做过&#xff09; sudo vim /etc/wsl.conf 内容必须是&#xff08;注意大小写&#xff09;&#xff1a; …

作者头像 李华
网站建设 2026/6/26 3:48:02

AI如何根据Mermaid文字格式,从而绘制精美准确的图像?

旧方法: AI直接生成图像,但是生成的速度很慢,文字错误不可用,出问题也不好微调 新的解决方法:让AI根据内容生成 Mermaid 图表,然后使用vscode安装插件展示,或者直接在线网页展示 展示方法一、vscode中根据内容绘制图 在 VS Code 中查看和编辑 Mermaid 图表非常简单,…

作者头像 李华
网站建设 2026/6/14 21:48:07

在工业视觉检测、设备智能监控等场景中,C#上位机凭借其稳定性强、硬件对接便捷的优势,成为工业控制系统的主流开发语言

在工业视觉检测、设备智能监控等场景中&#xff0c;C#上位机凭借其稳定性强、硬件对接便捷的优势&#xff0c;成为工业控制系统的主流开发语言&#xff1b;而Python凭借丰富的AI生态&#xff08;PyTorch、Ultralytics、TensorFlow&#xff09;&#xff0c;成为AI模型训练与推理…

作者头像 李华
网站建设 2026/6/19 17:28:11

Grafana Enterprise SCIM漏洞利用工具(CVE-2025-41115)

Grafana Enterprise SCIM漏洞利用工具&#xff08;CVE-2025-41115&#xff09; 项目描述 这是一个用于演示和验证CVE-2025-41115漏洞的Python利用脚本。该漏洞是Grafana Enterprise中一个关键&#xff08;CVSS 10.0&#xff09;的安全漏洞&#xff0c;影响其SCIM用户配置功能。…

作者头像 李华
网站建设 2026/6/26 3:47:34

细胞多尺度仿真软件:CellSys_(10).模型验证与优化

模型验证与优化 模型验证的重要性 在细胞多尺度仿真软件&#xff08;CellSys&#xff09;的开发和应用过程中&#xff0c;模型验证是一个至关重要的步骤。模型验证的目的是确保仿真模型能够准确地反映生物系统的实际行为&#xff0c;从而提高仿真的可靠性和准确性。验证过程通…

作者头像 李华