news 2026/11/2 11:15:21

ConcurrentHashMap 的实现原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ConcurrentHashMap 的实现原理

ConcurrentHashMap是HashMap的线程安全版本,核心目标是在高并发下提供安全的读写,同时尽量避免阻塞。它的实现经历了两个关键阶段:JDK 1.7 用“分段锁”降低锁粒度,JDK 1.8 改为“CAS + synchronized 锁单个桶”,并发度更高。

一、在 JDK1.7 中的实现原理

实现原理采用分段锁(Segment)机制。将数据分为多个 Segment(默认16个),每个 Segment 继承 ReentranLock,独立加锁。不同 Segment 可并发访问,提高了并发度。每个 Segment 内部维护一个 HashEntry 数组。

在 jdk1.7 中, ConcurrentHashMap 的结构如下:

HashEntry是ConcurrentHashMap的内部静态类,核心字段大致是:

static final class HashEntry<K,V> { final int hash; // key 的哈希值(final,不可变) final K key; // 键(final) volatile V value; // 值(volatile,保证可见性) volatile HashEntry<K,V> next; // 链表下一个节点(volatile) }

一句话总结:ConcurrentHashMap是一个 Segment 数组;每个 Segment 继承 ReentrantLock 当锁用,内部又持有一个HashEntry[]数组;HashEntry是数组里的节点,用链表串起来真正存 key-value。

二、在 JDK1.8 中的实现原理

在 JDK1.8 中,实现原理废弃了分段锁,改用 CAS + synchronized。底层数据结构与 HashMap 一致(数组+链表+红黑树)。锁粒度细化到每个数据元素(桶),使用 synchronized 对链表头节点或树根节点加锁,同时利用 CAS 进行无锁操作。并发度更高,且引入红黑树优化链表查询性能。

  1. 桶为空时,CAS 插入:如果计算出的桶位置是空的,直接用casTabAt(CAS 操作)把新节点放进去,不需要加锁。

  2. 桶非空时,锁住头节点:如果桶里已经有元素,就用synchronized锁住当前桶的头节点(f)。只要 hash 不冲突,线程操作的是不同的桶,就不会产生锁竞争。

  3. 读操作基本无锁:get方法通常不加锁,依靠Node中val和next的volatile语义来保证可见性。

JDK1.8 用锁单个桶头节点替代了锁整个段,并用CAS处理空桶插入,把锁的冲突概率降到了最低。

什么时候从链表变成红黑树?什么时候从红黑树退化为链表?

  • 当链表长度 >= 8 且数组长度 >= 64 时,链表转为红黑树
  • 当红黑树节点 <= 6 时,退化为链表。

三、补充知识

1、ReentranLock

ReentrantLock是 Java 在java.util.concurrent.locks包下提供的一个可重入的互斥锁,从 JDK 1.5 引入,用来替代synchronized的一部分场景。名字拆开就是两个关键词:Reentrant(可重入)+ Lock(锁)。

可重入是什么意思:

同一个线程可以多次获取同一把锁,不会把自己锁死。内部维护一个计数器:线程第一次lock()计数变 1,再次lock()变 2;每unlock()一次减 1,减到 0 才真正释放锁。

和 synchronized 的区别

维度synchronizedReentrantLock
层级JVM 关键字JDK 类(API 层)
加锁/释放自动(代码块结束自动释放)手动lock()/unlock(),必须finally释放
可重入是是
公平锁不支持支持(new ReentrantLock(true))
尝试加锁不支持支持tryLock()(可带超时,不阻塞或限时等待)
可中断不支持(等锁时不可中断)支持lockInterruptibly()
条件变量一个(wait/notify)多个Condition(newCondition())
锁状态查询不能能(isLocked()、getQueueLength()等)

为什么 ConcurrentHashMap 1.7 的 Segment 要继承它

static final class Segment<K,V> extends ReentrantLock implements Serializable

Segment 需要“自己就是一把锁”的能力,而ReentrantLock正好提供:

  • lock()/unlock()加解锁;

  • 可重入——同一线程在 Segment 内部操作时可能重复加锁,可重入避免死锁;

  • 比synchronized更灵活(比如可尝试加锁、可查询状态)。

所以ConcurrentHashMap1.7 里,每个 Segmentextends ReentrantLock,直接获得锁的全部能力,锁对象就是 Segment 自己,加锁就是segment.lock()。

2、CAS

CAS(Compare-And-Swap,比较并交换)是一条CPU 指令级的原子操作,不是 Java 发明的。它做三件事,且这三件事整体原子、不可被打断:

期待值是调用方自己给。典型模式是“读 → 算 → CAS 重试”:

CAS(内存地址 V, 期望值 A, 新值 B):
if (V 当前值 == A) {
V = B;
return true; // 交换成功
} else {
return false; // 交换失败,V 不动
}

  • V:要操作的内存地址(变量)。

  • A:期望值,调用方认为 V 现在应该是多少。

  • B:想写进去的新值。

只有V 当前值 == A时才写入 B;否则什么都不做,返回失败。

3、synchronized

synchronized是 JVM 直接支持的关键字(不是类库),提供三件事:

  1. 互斥:同一时刻只有一个线程能持有锁、进入临界区。

  2. 可见性:线程释放锁前对共享变量的修改,会刷新到主存;下一个拿到锁的线程能看到这些修改(相当于自带volatile的可见性语义)。

  3. 可重入:同一线程可以多次获取同一把锁,不会把自己锁死。

4、红黑树

红黑树是带颜色规则的自平衡二叉搜索树:五条规则(尤其“不能红红相连”和“黑高相等”)保证树高始终 O(log n),不会退化成链表;插入/删除靠变色 + 旋转修复,代价是常数次旋转,所以增删性能好,被TreeMap、HashMap/ConcurrentHashMap的桶广泛使用。

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

RAG 重排实战:正确证据找到了,为什么还是没进上下文?

RAG 重排实战&#xff1a;正确证据找到了&#xff0c;为什么还是没进上下文&#xff1f; 检索返回了 20 段内容&#xff0c;只取前 5 段送给模型&#xff1b;真正支持答案的证据在第 12 位。此时召回并非一无所获&#xff0c;问题出在最终选择。 重排&#xff08;Reranking&am…

作者头像 李华
网站建设 2026/11/2 11:07:02

三行命令跑通HTML转PDF:wkhtmltopdf 纯命令行实操手册

三行命令跑通HTML转PDF&#xff1a;wkhtmltopdf 纯命令行实操手册 【免费下载链接】wkhtmltopdf Convert HTML to PDF using Webkit (QtWebKit) 项目地址: https://gitcode.com/gh_mirrors/wk/wkhtmltopdf 网页要存档、报表要打印&#xff0c;复制粘贴总是差一口气&…

作者头像 李华
网站建设 2026/11/2 11:02:58

HarmonyOS 3DGS 滤镜切换实战:效果创建、参数管理与资源释放

HarmonyOS 3DGS 滤镜切换实战&#xff1a;效果创建、参数管理与资源释放 实际项目里&#xff0c;滤镜实例最容易出现的问题不是“接口不会调用”&#xff0c;而是状态分散、异步结果乱序、失败后没有回退。切换滤镜时画面稳定且旧效果被释放&#xff0c;需要把能力边界、生命周…

作者头像 李华
网站建设 2026/11/2 10:42:47

C++ 绘制画布标尺

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华