ConcurrentHashMap 深度解析:从分段锁到 CAS 的进化之路
一、引言HashMap 的线程安全困境HashMap 是 Java 中最常用的容器之一但它有一个致命缺陷——线程不安全。在多线程环境下HashMap 在扩容时可能形成环形链表导致get()操作陷入死循环CPU 飙升到 100%。那用Hashtable呢它所有方法都加了synchronized相当于给整张表上了一把大锁同一时刻只允许一个线程操作并发性能极差。于是ConcurrentHashMap应运而生。它既保证了线程安全又追求极高的并发性能是 Java 并发容器中最闪耀的明星。核心定位ConcurrentHashMap是一个线程安全的哈希表设计目标是在最小化更新操作对哈希表占用的同时保持与HashMap相当的空间消耗并支持多线程高效并发访问。从 Java 7 到 Java 8ConcurrentHashMap经历了一次彻底的重写代码量从 1000 多行暴涨到 6000 多行。下面我们从 JDK 8 的视角深入源码一探究竟。二、JDK 7 vs JDK 8架构的全面进化2.1 JDK 7分段锁Segment LockJava 7 中的ConcurrentHashMap采用了分段锁技术将数据分成多个Segment每个Segment独立加锁。Segment继承自ReentrantLock每个 Segment 是一把独立的锁默认 16 个 Segment理论上支持 16 个线程并发写入锁的粒度是整个 Segment一个 Segment 内的所有操作互斥2.2 JDK 8CAS synchronizedJava 8 彻底摒弃了Segment的设计采用了与HashMap相同的数据结构——数组 链表 红黑树并使用CAS synchronized保证线程安全。对比维度JDK 7JDK 8数据结构Segment数组 HashEntry数组 链表Node数组 链表 红黑树锁机制ReentrantLock分段锁CAS synchronized锁头节点锁粒度整个 Segment单个桶链表/树的首节点并发度固定默认16动态数组长度扩容参与单线程扩容多线程协助扩容JDK 8 的核心优势锁粒度从 Segment 缩小到单个桶节点只要 hash 不冲突不同桶的操作可以完全并行。三、核心数据结构与成员变量3.1 Node——基本存储节点Node是ConcurrentHashMap中最基础的存储单元static class NodeK,V implements Map.EntryK,V { final int hash; final K key; volatile V val; // volatile 保证可见性 volatile NodeK,V next; // volatile 保证可见性 Node(int hash, K key, V val, NodeK,V next) { this.hash hash; this.key key; this.val val; this.next next; } }关键设计val和next都使用了volatile修饰确保一个线程对节点的修改对其他线程立即可见。3.2 TreeNode——红黑树节点当链表长度超过阈值时链表会转换为红黑树。TreeNode继承自Node增加了红黑树所需的指针static final class TreeNodeK,V extends NodeK,V { TreeNodeK,V parent; // 父节点 TreeNodeK,V left; // 左子节点 TreeNodeK,V right; // 右子节点 TreeNodeK,V prev; // 前驱节点用于维持双向链表 boolean red; // 红/黑颜色标记 }3.3 TreeBin——红黑树代理注意桶位中存储的不是TreeNode对象而是TreeBin对象。TreeBin是红黑树的代理容器内部维护着红黑树的根节点root和双向链表的头节点first。3.4 ForwardingNode——扩容标记节点当某个桶的数据迁移完成后该桶位会被设置为ForwardingNode简称 FWD 节点其hash值为MOVED-1。后续其他线程看到这个标记就知道该桶正在扩容或已迁移完成。3.5 核心成员变量// 存储数据的数组volatile 保证可见性 transient volatile NodeK,V[] table; // 扩容时使用的新数组仅在扩容期间非空 private transient volatile NodeK,V[] nextTable; // 核心控制字段控制初始化和扩容 private transient volatile int sizeCtl; // 默认初始容量 16 private static final int DEFAULT_CAPACITY 16; // 最大容量 2^30 private static final int MAXIMUM_CAPACITY 1 30; // 负载因子 0.75固定不可修改 private static final float LOAD_FACTOR 0.75f; // 链表转红黑树阈值8 static final int TREEIFY_THRESHOLD 8; // 红黑树转链表阈值6 static final int UNTREEIFY_THRESHOLD 6; // 转红黑树的最小数组容量64小于此值优先扩容 static final int MIN_TREEIFY_CAPACITY 64;3.6 sizeCtl——最核心的控制字段sizeCtl是ConcurrentHashMap中出镜率最高的字段它的值在不同阶段代表不同含义sizeCtl 值含义0默认值尚未初始化-1正在初始化 table -1正在扩容低 16 位表示参与扩容的线程数如 -N 表示有 N-1 个线程参与 0初始化完成后的扩容阈值容量 × 0.75四、构造方法延迟初始化ConcurrentHashMap采用了延迟初始化策略——构造方法只计算容量并不真正创建 table 数组。// 无参构造什么也不做 public ConcurrentHashMap() { } // 指定初始容量的构造方法 public ConcurrentHashMap(int initialCapacity) { if (initialCapacity 0) throw new IllegalArgumentException(); // 计算大于 1.5 * initialCapacity 1 的最小 2 的幂 int cap tableSizeFor(initialCapacity (initialCapacity 1) 1); this.sizeCtl cap; // 只设置 sizeCtl不创建 table }延迟初始化的好处只有在第一次put时才真正创建数组避免了不必要的内存占用。tableSizeFor方法确保容量始终是2 的幂这是为了后续使用位运算(n - 1) hash替代取模运算提升性能。注意loadFactor虽然在构造方法中作为参数传入但计算完size后并未被保存为成员变量后续扩容阈值计算固定使用 0.75。五、put 方法线程安全的核心put方法是ConcurrentHashMap最复杂的部分涉及初始化、CAS 插入、锁扩容、链表/树操作等多个环节。5.1 入口与 hash 计算public V put(K key, V value) { return putVal(key, value, false); } final V putVal(K key, V value, boolean onlyIfAbsent) { // key 和 value 都不能为 null if (key null || value null) throw new NullPointerException(); // spread让高位参与寻址使 hash 更分散 int hash spread(key.hashCode()); int binCount 0; // 记录桶中元素个数用于判断是否树化 // 自旋无限循环直到操作成功 for (NodeK,V[] tab table;;) { NodeK,V f; int n, i, fh; // ... 四种情况处理 } }spread方法将key.hashCode()的高位与低位混合减少 hash 冲突static final int spread(int h) { return (h ^ (h 16)) HASH_BITS; }5.2 四种情况处理putVal的核心是一个自旋循环处理四种不同的情况Case 1table 未初始化if (tab null || (n tab.length) 0) tab initTable(); // 初始化 tableinitTable()通过 CAS 控制sizeCtl确保只有一个线程执行初始化private final NodeK,V[] initTable() { NodeK,V[] tab; int sc; while ((tab table) null || tab.length 0) { if ((sc sizeCtl) 0) // 其他线程正在初始化 Thread.yield(); else if (U.compareAndSetInt(this, SIZECTL, sc, -1)) { // CAS 将 sizeCtl 设为 -1当前线程获得初始化权 try { if ((tab table) null || tab.length 0) { int n (sc 0) ? sc : DEFAULT_CAPACITY; NodeK,V[] nt (NodeK,V[])new Node?,?[n]; table tab nt; sc n - (n 2); // 0.75 * n } } finally { sizeCtl sc; // 设置扩容阈值 } break; } } return tab; }Case 2桶位为空无 hash 冲突else if ((f tabAt(tab, i (n - 1) hash)) null) { // 使用 CAS 将新节点放入空桶位 if (casTabAt(tab, i, null, new NodeK,V(hash, key, value, null))) break; // CAS 成功跳出循环 }这里使用CAS 无锁操作不需要加锁是最高效的插入场景。Case 3桶位正在扩容FWD 节点else if ((fh f.hash) MOVED) tab helpTransfer(tab, f); // 当前线程协助扩容如果桶位的头节点是ForwardingNodehash MOVED说明该桶正在扩容迁移当前线程会协助扩容。Case 4正常插入链表或红黑树else { V oldVal null; synchronized (f) { // 锁住头节点 if (tabAt(tab, i) f) { // 双重检查 if (fh 0) { // 普通链表节点 binCount 1; for (NodeK,V e f;; binCount) { K ek; if (e.hash hash ((ek e.key) key || (ek ! null key.equals(ek)))) { oldVal e.val; if (!onlyIfAbsent) e.val value; break; } NodeK,V pred e; if ((e e.next) null) { pred.next new NodeK,V(hash, key, value, null); break; } } } else if (f instanceof TreeBin) { // 红黑树节点 NodeK,V p; binCount 2; if ((p ((TreeBinK,V)f).putTreeVal(hash, key, value)) ! null) { oldVal p.val; if (!onlyIfAbsent) p.val value; } } } } // 判断是否需要树化 if (binCount ! 0) { if (binCount TREEIFY_THRESHOLD) // 8 treeifyBin(tab, i); if (oldVal ! null) return oldVal; break; } }关键设计使用synchronized锁住桶的头节点f而不是锁整张表。这意味着只有操作同一个桶的线程才会竞争锁不同桶的操作完全并行。5.3 addCount元素计数与扩容触发插入完成后调用addCount增加元素数量并检查是否需要扩容addCount(1L, binCount);addCount内部会判断当前元素数量是否超过sizeCtl扩容阈值如果超过则触发transfer扩容。六、get 方法无锁读取的秘密get方法是ConcurrentHashMap性能的又一体现——全程不加锁。public V get(Object key) { NodeK,V[] tab; NodeK,V e, p; int n, eh; K ek; int h spread(key.hashCode()); if ((tab table) ! null (n tab.length) 0 (e tabAt(tab, (n - 1) h)) ! null) { if ((eh e.hash) h) { // 情况1首节点就是目标节点 if ((ek e.key) key || (ek ! null key.equals(ek))) return e.val; } else if (eh 0) { // 情况2hash 0可能是 TreeBin 或 ForwardingNode // - 如果是 ForwardingNode扩容中去 nextTable 中查找 // - 如果是 TreeBin遍历红黑树 return (p e.find(h, key)) ! null ? p.val : null; } // 情况3遍历链表 while ((e e.next) ! null) { if (e.hash h ((ek e.key) key || (ek ! null key.equals(ek)))) return e.val; } } return null; }为什么 get 不需要加锁volatile保证可见性Node的val和next都是volatile的写入的结果对所有线程立即可见table是volatile的数组引用本身保证可见性Node的hash和key是final的一旦创建不可变扩容时的特殊处理通过ForwardingNode.find()到新表查找这种设计使得get操作几乎不受锁竞争影响性能极高。七、扩容机制多线程协同作战扩容是ConcurrentHashMap最复杂的部分也是它区别于普通HashMap的核心优势——支持多线程协同扩容。7.1 扩容触发条件扩容在addCount方法中被触发if (check 0) { NodeK,V[] tab, nt; int n, sc; while (s (long)(sc sizeCtl) (tab table) ! null (n tab.length) MAXIMUM_CAPACITY) { int rs resizeStamp(n); if (sc 0) { // 已有线程在扩容 if ((sc RESIZE_STAMP_SHIFT) ! rs || sc rs 1 || sc rs MAX_RESIZERS || (nt nextTable) null || transferIndex 0) break; if (U.compareAndSetInt(this, SIZECTL, sc, sc 1)) transfer(tab, nt); // 当前线程协助扩容 } else if (U.compareAndSetInt(this, SIZECTL, sc, (rs RESIZE_STAMP_SHIFT) 2)) transfer(tab, null); // 当前线程是第一个发起扩容的 s sumCount(); } }7.2 transfer数据迁移transfer方法负责将旧 table 的数据迁移到新 table容量翻倍private final void transfer(NodeK,V[] tab, NodeK,V[] nextTab) { int n tab.length, stride; // 计算每个线程负责的桶位数步长 if ((stride (NCPU 1) ? (n 3) / NCPU : n) MIN_TRANSFER_STRIDE) stride MIN_TRANSFER_STRIDE; // 最小 16 if (nextTab null) { // 第一个发起扩容的线程创建新数组 try { NodeK,V[] nt (NodeK,V[])new Node?,?[n 1]; // 容量翻倍 nextTab nt; } catch (Throwable ex) { sizeCtl Integer.MAX_VALUE; return; } nextTable nextTab; transferIndex n; // 从最后一个桶开始分配任务 } int nextn nextTab.length; ForwardingNodeK,V fwd new ForwardingNodeK,V(nextTab); boolean advance true; boolean finishing false; // 自旋迁移数据 for (int i 0, bound 0;;) { // ... 分配任务、迁移数据 ... } }7.3 扩容的核心设计要点任务分片每个线程每次负责stride个桶的迁移默认 16从后往前迁移transferIndex记录全局迁移进度从高位向低位推进FWD 标记迁移完成的桶位设置为ForwardingNodehash 值为MOVED链表拆分原链表被拆分为两个链表分别放入新表的i和i n位置CAS 控制并发通过 CAS 修改sizeCtl和transferIndex协调多线程多线程扩容的优势扩容时间随着参与线程数增加而缩短充分利用多核 CPU 能力。八、链表 ↔ 红黑树转换8.1 链表转红黑树当链表长度 ≥8且数组长度 ≥64时链表转换为红黑树private final void treeifyBin(NodeK,V[] tab, int index) { NodeK,V b; int n, sc; if (tab ! null) { if ((n tab.length) MIN_TREEIFY_CAPACITY) // 64 tryPresize(n 1); // 优先扩容 else if ((b tabAt(tab, index)) ! null b.hash 0) { synchronized (b) { if (tabAt(tab, index) b) { // 将链表节点转为 TreeNode然后构建红黑树 TreeNodeK,V hd null, tl null; for (NodeK,V e b; e ! null; e e.next) { TreeNodeK,V p new TreeNodeK,V(e.hash, e.key, e.val, null, null); if ((p.prev tl) null) hd p; else tl.next p; tl p; } // 用 TreeBin 替代原链表头节点 setTabAt(tab, index, new TreeBinK,V(hd)); } } } } }为什么阈值是 8源码注释指出在理想情况下桶中节点数服从泊松分布一个桶中出现 8 个节点的概率仅为0.00000006。因此 8 是一个足够保守的阈值。8.2 红黑树转链表当红黑树节点数减少到 ≤6时树退化为链表。九、总结ConcurrentHashMap是 Java 并发容器中最闪耀的明星它的设计体现了 Java 在并发编程领域的不断进步核心要点回顾数据结构数组 链表 红黑树与 HashMap 1.8 保持一致线程安全机制CAS无竞争场景synchronized锁头节点摒弃了 Segment 分段锁锁粒度从 Segment 级别细化到单个桶节点并发度大幅提升无锁读取get方法全程不加锁依赖volatile保证可见性多线程扩容支持多线程协同完成数据迁移充分利用多核 CPU延迟初始化table 在第一次put时才真正创建put操作流程:首先先判断key和value是否为空如果为空则抛出异常然后计算哈希值进入自旋操作(CAS如果table为空则CAS初始化table如果是正在扩容则协助扩容如果桶为空则直接CAS插入如果桶位有节点则synchronized锁住头节点遍历链表有相同的则替换没有则插入然后判断是否需要树化。get操作流程:首先计算哈希值定位桶检查首节点相同则直接返回如果是fwd扩容中 则去nextable中寻找如果是treebin则去树中遍历否则去链表中遍历。性能对比容器线程安全并发性能适用场景HashMap❌最高单线程环境Hashtable✅全表锁极低遗留代码ConcurrentHashMap (JDK 7)✅分段锁中中等并发ConcurrentHashMap (JDK 8)✅CAS 锁头节点高高并发首选ConcurrentHashMap的成功源于它对并发性能和线程安全的精妙平衡。无论是面试还是日常开发深入理解它的设计思想都能让你写出更高效、更安全的并发代码。

相关新闻

Vue路由守卫全解析:从权限控制到性能优化的实战指南

Vue路由守卫全解析:从权限控制到性能优化的实战指南

1. 项目概述:为什么路由守卫是Vue应用的门神在开发一个Vue应用,特别是单页面应用(SPA)时,我们经常会遇到这样的场景:用户没登录,却直接通过URL访问后台管理页面;或者用户在填写一个复…

2026/8/2 3:40:01阅读更多 →
研学旅行实训室技术架构:VR全景底座+同伴互动AI七维测评+3DGS采集+数字人

研学旅行实训室技术架构:VR全景底座+同伴互动AI七维测评+3DGS采集+数字人

技术背景:研学旅行专业的实训需求拆解 研学旅行管理与服务专业(专业代码540105)的核心能力要求可拆解为六个维度:课程设计、活动组织、研学指导、安全管理、基地运营、新媒体营销。每个维度对实训环境的技术支撑有不同要求: 课程设计 → 需要目的地考察数据底座(VR全景资…

2026/8/2 3:40:01阅读更多 →
游戏开发架构优化:从ECS到事件驱动,解决音游性能与维护难题

游戏开发架构优化:从ECS到事件驱动,解决音游性能与维护难题

最近在游戏开发社区里,一个名为“DxS《blue》Rhythm hive 极困”的项目标题引起了不少讨论。乍一看,这个标题像是由几个看似不相关的词汇拼接而成——“DxS”、“blue”、“Rhythm hive”、“极困”。很多开发者第一反应可能是:这又是一个炫技…

2026/8/2 3:40:00阅读更多 →
Claude 5传闻背后:AI编程模型能力评估与智能体工程化实践

Claude 5传闻背后:AI编程模型能力评估与智能体工程化实践

1. 从“史诗级泄露”到理性审视:Claude 5传闻的真相与价值最近几天,AI圈子里关于“Claude 5史诗级泄露”的消息传得沸沸扬扬,各种截图、评测数据满天飞,标题一个比一个炸裂,什么“史上最强编程模型”、“评测炸裂”、“…

2026/8/2 7:33:13阅读更多 →
电子墨水屏驱动板硬件连接与软件驱动全解析

电子墨水屏驱动板硬件连接与软件驱动全解析

1. 从拆箱到点亮:ePaper驱动板初印象如果你刚拿到一块ePaper驱动板,看着上面密密麻麻的引脚和芯片,可能会有点懵。这玩意儿怎么用?它和普通的LCD屏驱动板有什么区别?别急,我当初也是这么过来的。简单来说&a…

2026/8/2 7:33:13阅读更多 →
大模型API成本优化实战:从Token经济到高效调用的全链路指南

大模型API成本优化实战:从Token经济到高效调用的全链路指南

1. 项目概述:从“天价账单”看大模型API的成本与效率博弈最近,一个关于“龙虾之父”在OpenAI上每月烧掉940万元Token的讨论,在技术圈里激起了不小的波澜。这个标题乍一看很唬人,但背后折射出的,其实是每一个正在或即将…

2026/8/2 7:33:13阅读更多 →
MockGPS完整指南:免费解锁Android虚拟定位终极方案

MockGPS完整指南:免费解锁Android虚拟定位终极方案

MockGPS完整指南:免费解锁Android虚拟定位终极方案 【免费下载链接】MockGPS Android application to fake GPS 项目地址: https://gitcode.com/gh_mirrors/mo/MockGPS MockGPS是一款功能强大的开源Android虚拟定位工具,能够轻松修改设备的GPS定位…

2026/8/2 7:33:13阅读更多 →
003003001_Grid控件

003003001_Grid控件

003003001_Grid控件 摘要:本文系统讲解 WPF 中最核心的布局控件 Grid,涵盖其继承链、类定义、三种尺寸模式(固定/Auto/星号)、附件属性绑定方式,以及标准使用步骤。通过基础登录表单、复杂界面分区、比例网格三个实战示…

2026/8/2 7:33:13阅读更多 →
深度学习数据增强实战:从原理到Albumentations高效实现

深度学习数据增强实战:从原理到Albumentations高效实现

1. 项目概述:为什么数据增强是深度学习的“必修课”? 刚入行做深度学习那会儿,我花了好几个月调一个图像分类模型,准确率死活卡在85%上不去。后来一位前辈看了一眼我的训练日志,只问了一句:“你的数据量够吗…

2026/8/2 7:31:13阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/2 0:00:10阅读更多 →
限时公开!某头部SaaS公司内部AI模板工厂架构文档(含5类行业模板源码+性能压测报告)

限时公开!某头部SaaS公司内部AI模板工厂架构文档(含5类行业模板源码+性能压测报告)

更多请点击: https://intelliparadigm.com 第一章:AI模板批量生成的核心价值与落地全景 AI模板批量生成正从实验性工具演进为现代软件工程的关键基础设施。它通过语义理解、上下文感知与结构化约束,将重复性高、模式明确的代码/文档/配置生成…

2026/8/2 0:00:12阅读更多 →
如何快速找回消失的网页:Web Archives浏览器扩展终极指南

如何快速找回消失的网页:Web Archives浏览器扩展终极指南

如何快速找回消失的网页:Web Archives浏览器扩展终极指南 【免费下载链接】web-archives Browser extension for viewing archived and cached versions of web pages, available for Chrome, Edge and Safari 项目地址: https://gitcode.com/gh_mirrors/we/web-a…

2026/8/2 0:00:13阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/2 0:00:10阅读更多 →
限时公开!某头部SaaS公司内部AI模板工厂架构文档(含5类行业模板源码+性能压测报告)

限时公开!某头部SaaS公司内部AI模板工厂架构文档(含5类行业模板源码+性能压测报告)

更多请点击: https://intelliparadigm.com 第一章:AI模板批量生成的核心价值与落地全景 AI模板批量生成正从实验性工具演进为现代软件工程的关键基础设施。它通过语义理解、上下文感知与结构化约束,将重复性高、模式明确的代码/文档/配置生成…

2026/8/2 0:00:12阅读更多 →
如何快速找回消失的网页:Web Archives浏览器扩展终极指南

如何快速找回消失的网页:Web Archives浏览器扩展终极指南

如何快速找回消失的网页:Web Archives浏览器扩展终极指南 【免费下载链接】web-archives Browser extension for viewing archived and cached versions of web pages, available for Chrome, Edge and Safari 项目地址: https://gitcode.com/gh_mirrors/we/web-a…

2026/8/2 0:00:13阅读更多 →
无损视频剪辑终极指南:如何实现快速高效的多媒体处理

无损视频剪辑终极指南:如何实现快速高效的多媒体处理

无损视频剪辑终极指南:如何实现快速高效的多媒体处理 【免费下载链接】lossless-cut The swiss army knife of lossless video/audio editing 项目地址: https://gitcode.com/gh_mirrors/lo/lossless-cut 在数字媒体创作领域,视频编辑处理的质量损…

2026/8/2 1:29:34阅读更多 →
AI辅助本科论文写作:8大工具评测与高效使用指南

AI辅助本科论文写作:8大工具评测与高效使用指南

1. 本科生论文写作的AI辅助现状本科毕业论文是每个大学生必须跨越的一道坎。记得我当年写论文时,光是文献检索就花了整整两周时间,打印的参考文献堆满了半个书桌。如今AI技术的发展为学术写作带来了革命性变化,合理使用这些工具可以节省80%以…

2026/8/2 2:32:55阅读更多 →
如何快速配置大麦自动抢票系统:从零开始搭建Python抢票助手

如何快速配置大麦自动抢票系统:从零开始搭建Python抢票助手

如何快速配置大麦自动抢票系统:从零开始搭建Python抢票助手 【免费下载链接】ticket-purchase 大麦自动抢票,支持人员、城市、日期场次、价格选择 项目地址: https://gitcode.com/GitHub_Trending/ti/ticket-purchase 还在为抢不到热门演唱会门票…

2026/8/2 2:09:20阅读更多 →