JDK 8 ConcurrentHashMap 如何保证线程安全?
未标记
考查重点:CAS、桶锁与协作扩容。JDK8 以可见性访问、CAS 与桶级 synchronized 管并发,扩容用转发节点和协作迁移;不是旧版分段锁
本题目录
参考答案
核心回答
JDK 8 的 ConcurrentHashMap 使用**具有内存可见性保证的访问、CAS 和桶级 synchronized**共同保证容器的并发安全,并通过转发节点与协作迁移处理扩容。以下实现细节以 OpenJDK 8u412 为例。
它的主体是 Node 数组,冲突桶可采用链表或树结构,已经不再用 JDK 7 的 Segment 数组作为并发控制主体:
- 初始化与空桶插入用
CAS竞争。 同一个空桶只有一个线程能成功发布节点,失败者重新检查状态。 - 非空桶的普通更新在桶内同步。 锁住桶首节点并复查后,修改链表或树;不同桶的更新通常可以并行。
- 读取依赖安全发布和可见性。 桶位使用具有
volatile语义的访问,节点的值、后继指针为volatile,普通get不获取写入使用的桶监视器锁。 - 扩容有专门的并发协议。 线程分段领取迁移任务,已迁移的旧桶放置转发节点,查询转向新表,遇到迁移的更新线程可协助扩容。
因此,它既不是“一把锁锁整张表”,也不是“全部操作无锁”。这些保证主要面向容器操作;多个调用组成的业务流程仍需明确原子范围。
put 为什么需要 CAS 和桶锁两种路径
表还未初始化时,通过 sizeCtl 的 CAS 协调由一个线程完成初始化,其他线程重试。桶为空时直接尝试 CAS 放入新节点,避免两个线程都认为桶为空后互相覆盖。
桶非空时,普通 put 会在当前桶首节点上进入 synchronized;进入后还要确认数组该位置仍是这个节点,因为等待锁期间可能发生迁移或其他结构变化。只有状态仍匹配,才更新已有键的值、追加链表节点或执行树桶更新;否则重新检查。
锁粒度是桶而不是键:两个不同键如果落在同一个桶,仍可能竞争。树化可以改善长冲突链的查找,但不是线程安全的来源,也不会消除热点桶的竞争。
get 为什么通常不需要桶锁
不能只说“table 是 volatile,所以安全”。数组引用为 volatile,并不会自动让每个数组元素也变为 volatile;JDK 8 还通过 tabAt 等方法以 volatile 语义读写桶位,配合节点的 final、volatile 字段完成发布和读取。
普通查询定位桶后,检查首节点、遍历链表,或者交给特殊节点查找。遇到 ForwardingNode 会沿它找到新表;树桶由 TreeBin 管理,在树结构调整时还有额外的读写协调,必要时沿链表查询,不能一概概括成“get 只读几个 volatile 字段”。
API 保证某键的更新与读取到该更新值的查询之间存在 happens-before 关系。但与更新重叠的查询不代表能看到业务期望的未来结果,多次查询也不会组成全表快照。
扩容时怎样保持可访问
扩容不是把所有线程停下来后整体搬表。迁移线程通过 CAS 领取旧表的一段桶位;迁移非空桶时使用该桶的同步保护,将数据放到新表相应位置,随后在旧桶发布 ForwardingNode,表示该桶已迁移。
读请求据此转向新表,更新请求可以帮助迁移并继续查找。分散计数则通过 baseCount 与 counterCells 减少所有线程争抢同一计数器的压力。这些机制协调的是扩容与统计,不提供整个 Map 的事务隔离;并发更新期间的 size 等聚合结果可能反映暂时状态。
追问:为什么安全容器仍会丢一次业务更新
两个线程先 get 到同一计数值,再分别加一、put 回去,单次调用都安全,组合仍可能相互覆盖。单键条件更新可按需求使用 putIfAbsent、compute、merge 等原子方法;长计算、远程调用以及多个键之间的约束,还需要单独设计。Map 中保存可变对象,也不会自动保护对象内部的并发修改。
参考资料
- 国内:JavaGuide:ConcurrentHashMap 源码分析:用于串联 JDK 8 的初始化、put、get 和分散计数,解释各机制分别保护哪一步。
- 国内:JavaGuide:Java 集合常见面试题(下):用于对比 Segment 与桶级同步,并说明单次操作和业务复合操作的区别。
- 国内:pdai:ConcurrentHashMap 详解:用于解释迁移任务分配、转发节点与查询新表的衔接,补充扩容期间的处理过程。
- 国外:Oracle:ConcurrentHashMap,Java SE 8:用于核对读取与更新的可见性约定、非快照遍历及并发统计的边界。
- 国外:Oracle:java.util.concurrent 包,Java SE 8:用于说明并发容器的操作语义与内存一致性,解释安全交接对象和单一全局锁的区别。
相关面经
13 篇启云方
AI 应用开发工程师ConcurrentHashMap 如何保证线程安全?读操作为什么不加锁?
小红书
国际化开发工程师● ConcurrentHashMap的实现机制
字节跳动
后端开发工程师HashMap 为什么线程不安全?线程安全的替代方案如何实现?
中科星图维天信
后端开发ConcurrentHashMap 是如何保证线程安全的?
曹操出行
后端实习concurrentHashMap比HashMap优点,解决了什么问题,底层怎么实现的
展开剩余 8 篇面经
拼多多、荣耀、立元科技、得物、华橙
concurrenthashmap安全性
去哪儿
AI 应用开发阿里巴巴
AI 应用开发工程师ConcurrentHashMap 在 JDK 8 中如何保证线程安全?
小红书
AI Native 全栈开发工程师ConcurrentHashMap CAS + synchronized 实现
万兴科技
Android 客户端开发工程师(AI 方向)ConcurrentHashMap介绍一下,1.8之后的优化有什么好处
法大大
Java 开发工程师ConcurrentHashMap怎么保证线程安全的
阿里巴巴
后端开发工程师(AI)ConcurrentHashMap呢?它是怎么保证线程安全的?
美团
全栈开发工程师ConcurrentHashMap底层





