JDK 8 ConcurrentHashMap 如何保证线程安全?

考查重点:CAS、桶锁与协作扩容。JDK8 以可见性访问、CAS 与桶级 synchronized 管并发,扩容用转发节点和协作迁移;不是旧版分段锁

本题目录

参考答案

核心回答

JDK 8 的 ConcurrentHashMap 使用**具有内存可见性保证的访问、CAS 和桶级 synchronized**共同保证容器的并发安全,并通过转发节点与协作迁移处理扩容。以下实现细节以 OpenJDK 8u412 为例。

它的主体是 Node 数组,冲突桶可采用链表或树结构,已经不再用 JDK 7 的 Segment 数组作为并发控制主体:

  1. 初始化与空桶插入用 CAS 竞争。 同一个空桶只有一个线程能成功发布节点,失败者重新检查状态。
  2. 非空桶的普通更新在桶内同步。 锁住桶首节点并复查后,修改链表或树;不同桶的更新通常可以并行。
  3. 读取依赖安全发布和可见性。 桶位使用具有 volatile 语义的访问,节点的值、后继指针为 volatile,普通 get 不获取写入使用的桶监视器锁。
  4. 扩容有专门的并发协议。 线程分段领取迁移任务,已迁移的旧桶放置转发节点,查询转向新表,遇到迁移的更新线程可协助扩容。

因此,它既不是“一把锁锁整张表”,也不是“全部操作无锁”。这些保证主要面向容器操作;多个调用组成的业务流程仍需明确原子范围。

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 中保存可变对象,也不会自动保护对象内部的并发修改。

参考资料

相关面经

13 篇

启云方

AI 应用开发工程师
一面
查看原面经 →
ConcurrentHashMap 如何保证线程安全?读操作为什么不加锁?

小红书

国际化开发工程师
校招秋招一面
查看原面经 →
● ConcurrentHashMap的实现机制

字节跳动

后端开发工程师
一面
查看原面经 →
HashMap 为什么线程不安全?线程安全的替代方案如何实现?

中科星图维天信

后端开发
实习
查看原面经 →
ConcurrentHashMap 是如何保证线程安全的?

曹操出行

后端实习
实习
查看原面经 →
concurrentHashMap比HashMap优点,解决了什么问题,底层怎么实现的
展开剩余 8 篇面经

拼多多、荣耀、立元科技、得物、华橙

面经合集
查看原面经 →
concurrenthashmap安全性

去哪儿

AI 应用开发
查看原面经 →
hashmap是线程安全的吗,concurrenthashmap在jdk8中是如何实现线程安全的?
追问
追问:1)concurrenthashmap在put元素时,如果定位到的数组槽位是空的,他是用什么方式保证线程安全写入的?
追问
当数组槽位已经存在元素发生hash冲突时,sychornized锁的对象是什么

阿里巴巴

AI 应用开发工程师
秋招一面
查看原面经 →
ConcurrentHashMap 在 JDK 8 中如何保证线程安全?

小红书

AI Native 全栈开发工程师
日常实习一面、二面
查看原面经 →
ConcurrentHashMap CAS + synchronized 实现

万兴科技

Android 客户端开发工程师(AI 方向)
实习一面
查看原面经 →
ConcurrentHashMap介绍一下,1.8之后的优化有什么好处

法大大

Java 开发工程师
实习一面
查看原面经 →
ConcurrentHashMap怎么保证线程安全的

阿里巴巴

后端开发工程师(AI)
实习一面
查看原面经 →
ConcurrentHashMap呢?它是怎么保证线程安全的?

美团

全栈开发工程师
一面
查看原面经 →
ConcurrentHashMap底层

相关题目

4 题