《Java 100 天进阶之路》第49篇:ConcurrentHashMap原理(2026版) 第49篇ConcurrentHashMap原理2026版系列导航《Java 100 天进阶之路》完整目录 |⬅️ 上一篇第48篇HashMap源码全解下 |➡️ 下一篇第50篇阻塞队列与并发容器待发布文章目录第49篇ConcurrentHashMap原理2026版️ 本文阅读地图3 分钟速览一、核心知识点二、生活类比从“小杂货铺”到“智能超市”三、JDK 7 回顾3.1 核心结构3.2 JDK 7 的缺陷为什么废弃四、JDK 8CAS synchronized 桶级锁重点4.1 为什么废弃分段锁4.2 核心数据结构4.3 put 流程面试高频4.4 get 流程4.5 并发扩容机制JDK 8 最精妙设计4.6 计数机制size() 为什么是“弱一致”五、JDK 7 vs JDK 8 完整对比六、与 HashMap / Hashtable 对比七、生产级避坑清单八、面试高频考点 面试官追问陷阱加分题九、练习题 你的学习进度 下一篇文章预告️ 本文阅读地图3 分钟速览前两篇彻底拿下了 HashMap 源码但 HashMap线程不安全。高并发下用什么ConcurrentHashMap 给出了标准答案模块核心问题一句话回答为什么需要它HashMap 并发有什么问题JDK 7 扩容死循环、数据覆盖、size 不准JDK 7 回顾分段锁怎么工作简要Segment 数组每个 Segment 一把锁默认 16 段JDK 8 实现为什么抛弃分段锁改为Node 数组 CAS synchronized锁粒度细化到桶并发扩容扩容时其他线程怎么办多线程协助迁移旧桶标记ForwardingNode计数方式size()准确吗弱一致性用baseCountCounterCell近似统计面试最爱问高频考点有哪些见文末 小节一、核心知识点设计目标高并发下的线程安全 Map兼顾性能与安全JDK 7 架构简要Segment 分段锁继承ReentrantLock HashEntry 数组 链表JDK 8 架构重点Node 数组 链表/红黑树 CAS synchronized桶级锁并发扩容多线程协助迁移ForwardingNode占位 sizeCtl状态控制计数机制baseCountCounterCell[]类似LongAdder弱一致性size()、迭代器不保证强一致性二、生活类比从“小杂货铺”到“智能超市”老王开了家店一开始是HashMap 小杂货铺店里就 1 个货架数组商品按名字首字母放。问题很大——一旦有人整理货架扩容就得挂“暂停营业”的牌子全局锁顾客只能排队等。后来升级成ConcurrentHashMap 智能超市JDK 7 阶段分区域管理超市分成 3 个区域Segment 数组每个区域配 1 个保安ReentrantLock锁。顾客买“苹果”先算该去哪个区域只需要这个区域的保安放行其他区域完全不受影响。但同一区域的商品如果叠在一起哈希冲突拿“苹果”时还是会挡住“牛油果”。JDK 8 阶段精细化到货架层去掉区域保安直接把货架分成无数层Node 数组每一层都有自己的“小锁”synchronized。如果这一层没人直接拿CAS 无锁如果有人正在拿只锁这一层。如果某一层商品堆太多链表 ≥ 8自动改成“旋转货架”红黑树找商品更快。一句话总结锁粒度越来越小——从“全表锁 → 分段锁 → 桶级锁”兼顾线程安全和效率。三、JDK 7 回顾说明JDK 7 在 2026 年已基本上退出生产环境以下内容仅用于面试对比理解不必深入实现细节。3.1 核心结构Segment 数组核心锁容器每个 Segment 继承ReentrantLock默认 16 个HashEntry 数组每个 Segment 内部维护一个 HashEntry 数组操作流程put定位 Segment → 获取锁 → 操作链表 → 释放锁get不加锁依赖volatile3.2 JDK 7 的缺陷为什么废弃缺陷说明内存开销大16 个 Segment 自带锁、计数、对象头占用大量内存并发度受限同一 Segment 内多桶无法并行最大并发度 Segment 数量固定 16扩容效率低只能单 Segment 独立扩容无法多线程协作结构复杂三层嵌套Segment → HashEntry → 链表维护成本高四、JDK 8CAS synchronized 桶级锁重点4.1 为什么废弃分段锁JDK 8 彻底重构核心原因内存开销大16 个 Segment 自带锁、计数、对象头占用大量内存并发度受限同一 Segment 内多桶无法并行最大并发度被 Segment 数量限制synchronized优化成熟JDK 6 已升级为偏向锁→轻量级锁→重量级锁由 JVM 托管代码更简洁性能不输ReentrantLock4.2 核心数据结构核心字段NodeK,V[] table主哈希表桶数组NodeK,V[] nextTable扩容时的新表volatile int sizeCtl核心控制初始化、扩容阈值与状态volatile int transferIndex多线程扩容分工指针CounterCell[] counterCellsbaseCount高并发计数类似LongAdder三种节点类型Node链表节点val和next为volatileTreeNode红黑树节点链表过长时树化ForwardingNode扩容占位标记hash值为MOVED4.3 put 流程面试高频① 计算 hash定位到桶 ② 检查 table 是否初始化 → 未初始化则 CAS 初始化 ③ 桶位为空 → CAS 直接插入无锁快路径 ④ 桶位不为空 → 检查头节点 hash 是否为 MOVED ├─ 是 → 协助扩容 └─ 否 → synchronized(头节点) 锁住该桶 ├─ 链表遍历替换或尾插 └─ 红黑树树节点插入 ⑤ 链表长度 ≥ 8 且数组容量 ≥ 64 → 树化 ⑥ 元素数量超过阈值 → 触发扩容核心设计思想桶为空 →CAS 无锁最快路径无竞争桶不为空 →synchronized只锁桶头不同桶完全并行4.4 get 流程计算 hash定位桶桶为空 → 返回 null桶不为空 → 遍历链表/红黑树查找全程无锁依赖volatile保证可见性4.5 并发扩容机制JDK 8 最精妙设计触发条件元素数量超过容量 × 0.75核心流程单线程创建新表第一个检测到需要扩容的线程创建nextTable容量翻倍多线程协助迁移其他读写线程遇到ForwardingNode会主动参与迁移迁移标记已迁移的旧桶放置ForwardingNodehash MOVED数据迁移每个桶的元素按(hash oldCap)拆分为低位原位和高位原索引 oldCap完成所有桶迁移完毕替换table引用ForwardingNode 的作用表示该桶已迁移完成遇到它的线程会协助扩容读写不阻塞保证扩容期间其他线程仍可正常访问并发扩容为什么高效不是单线程苦力而是多线程协作完成通过 CAS 控制sizeCtl协调分工transferIndex指针分配迁移任务4.6 计数机制size() 为什么是“弱一致”publicintsize(){longsumbaseCount;if(counterCells!null){for(CounterCellcell:counterCells)sumcell.value;// 累加所有 CounterCell}return(int)Math.min(sum,Integer.MAX_VALUE);}设计原理baseCount无竞争时直接 CAS 更新CounterCell[]竞争激烈时每个线程更新自己的 CounterCell类似LongAdder的“分段计数”思路避免全局计数器的竞争弱一致性size()返回近似值不保证强一致调用期间可能有并发增删结果瞬间过时这是为了性能而主动放弃强一致性五、JDK 7 vs JDK 8 完整对比维度JDK 7JDK 8锁机制ReentrantLockSegment 锁CAS synchronized桶级锁数据结构Segment 数组 → HashEntry 数组 → 链表Node 数组 → 链表/红黑树锁粒度Segment 级默认 16 段桶级每个桶一把小锁扩容方式单 Segment 独立扩容多线程协助全局扩容计数方式每个 Segment 单独维护 countbaseCountCounterCell[]查询复杂度最坏 O(n)长链表最坏 O(log n)红黑树初始化时机构造时初始化懒加载第一次 put 时 CAS 初始化六、与 HashMap / Hashtable 对比对比维度HashMapHashtableConcurrentHashMap线程安全❌ 不安全✅synchronized全表锁✅ 桶级锁 CASnull 键/值✅ 允许❌ 不允许❌ 不允许并发度不适用极低读也锁极高读无锁写细粒度迭代器快速失败快速失败弱一致性性能单线程最高最差接近 HashMap七、生产级避坑清单✅ ConcurrentHashMap 生产环境使用规范 1. 不要用 HashMap 做多线程共享 → 用 ConcurrentHashMap 2. 不要用 null 键/值 → 会直接抛 NPE 3. size() 不精确 → 不要用于精确计数如金额统计 4. 迭代器不保证强一致 → 遍历期间可能有增删 5. 复合操作先 get 再 put非原子 → 用 compute/merge 等方法 6. 批量操作putAll非原子 → 需要外部同步或分批次八、面试高频考点Q1ConcurrentHashMap 和 Hashtable 的区别Hashtable用synchronized锁整个方法全表锁并发度极低。ConcurrentHashMapJDK 7 用分段锁JDK 8 用 CAS 桶级synchronized读操作基本无锁并发度远高于Hashtable。Hashtable是遗留类新代码优先用ConcurrentHashMap。Q2JDK 7 和 JDK 8 的 ConcurrentHashMap 核心区别锁机制JDK 7 用ReentrantLock分段锁JDK 8 用 CAS synchronized桶级锁。数据结构JDK 7 是 Segment HashEntry 链表JDK 8 是 Node 链表/红黑树。扩容JDK 7 单 Segment 扩容JDK 8 多线程协助全局扩容。Q3ConcurrentHashMap 为什么不允许 null 键/值设计上有歧义——map.get(key)返回 null 时无法区分是 key 不存在还是 value 本身就是 null。在非并发场景下可用containsKey区分但在并发环境中两次调用之间数据可能已变化这种区分失去意义。Q4size()方法准确吗不准确返回的是近似值。因为baseCount和CounterCell的累加过程中仍有并发修改。这是为了性能主动放弃强一致性。Q5ConcurrentHashMap 如何实现线程安全JDK 8 核心设计①CAS 无锁桶为空时直接 CAS 插入②synchronized桶级锁桶不为空时只锁桶头节点③读操作无锁依赖volatile保证可见性④并发扩容多线程协助迁移ForwardingNode占位。Q6扩容时其他线程可以读写吗可以。已迁移的旧桶放置ForwardingNodehash MOVED其他线程遇到该节点会协助扩容。扩容期间读写均不阻塞只是可能触发额外的迁移工作。Q7ConcurrentHashMap 的迭代器是 fail-fast 还是 fail-safe两者都不是是弱一致性迭代器。遍历期间其他线程的修改不会抛ConcurrentModificationException但迭代器也不保证能看到最新的修改。 面试官追问陷阱加分题追问1“JDK 8 为什么用synchronized而不是ReentrantLock”synchronized在 JDK 6 已经过锁升级优化偏向→轻量→重量由 JVM 自动管理锁的升级与释放代码更简洁无需手动lock()/unlock()。在低竞争场景下性能不输ReentrantLock。且ConcurrentHashMap的场景不需要公平锁——ReentrantLock默认也是非公平锁公平锁反而会降低吞吐量。核心原因是 JVM 对synchronized的深度优化锁消除、锁粗化以及代码简洁性。追问2“ConcurrentHashMap的put流程中CAS 失败后会怎样”CAS 失败说明有竞争其他线程已抢先完成了该桶的初始化或插入。当前线程会自旋重试——重新定位桶位再次检查状态根据新状态走对应的分支桶为空则再次 CAS、桶不为空则synchronized、遇到ForwardingNode则协助扩容。整个过程不会阻塞通过自旋重试实现轻量级并发控制。追问3“ConcurrentHashMap的size()不精确那如何获得精确大小”无法获得精确大小——这是ConcurrentHashMap的设计取舍。如果需要精确计数可以用AtomicLong外部计数器配合put/remove同步更新或使用Collections.synchronizedMap但会牺牲并发度。实际项目中99% 的场景只需要近似值做监控或统计精确值需求往往可以通过业务设计规避。九、练习题对比分析JDK 7 的分段锁和 JDK 8 的桶级锁有什么区别为什么 JDK 8 废弃了分段锁源码推导ConcurrentHashMap扩容时ForwardingNode的作用是什么为什么其他线程看到它要协助扩容场景设计某系统使用ConcurrentHashMap作为缓存需要统计缓存中元素个数但发现size()返回的值经常与实际不符。为什么如何解决代码分析下面的代码在并发场景下有什么问题如何优化ConcurrentHashMapString,IntegermapnewConcurrentHashMap();if(!map.containsKey(key)){map.put(key,1);}else{map.put(key,map.get(key)1);}思路containsKeygetput是复合操作不是原子的。应使用compute、merge或computeIfAbsent等原子方法。 你的学习进度当前第49篇 / 共108篇 ·进阶篇集合框架源码解析第45~50篇✅ 已完成基础篇44篇 第45~49篇 正在学第49篇⏳ 待学习第50~108篇 完整目录 学习指南 | 订阅本专栏不错过每一篇 下一篇文章预告下一篇《第50篇阻塞队列与并发容器》内容简介ArrayBlockingQueuevsLinkedBlockingQueuevsSynchronousQueueCopyOnWriteArrayList读写分离原理ConcurrentLinkedQueueCAS 无锁队列线程池中的阻塞队列选型看完第45~50篇集合框架源码系列正式收官《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇建议收藏 关注一起100天拿offer 点击关注我更新后第一时间收到推送