Java 8 废弃 Segment 是因锁粒度粗、段数固定导致热点竞争,内存冗余高,且难以支持树化与协作扩容;改用 CAS + synchronized 锁单个桶头节点,实现按需锁定与无锁读写。

Java 8 中的 ConcurrentHashMap 彻底移除了 JDK 7 的 Segment 分段锁机制,改用更轻量、更灵活的 CAS + synchronized 组合实现线程安全,核心目标是减少锁粒度、提升并发性能。
为什么废弃 Segment?
Segment 是 JDK 7 中模拟“分段哈希表”的设计:把整个哈希表分成多个固定段(默认 16 个),每个 Segment 独立加锁。虽然比全局锁好,但仍有明显缺陷:
- Segment 数量固定,扩容时无法动态调整,容易造成部分段过热(大量线程争抢同一段)
- 每次操作(如
get)仍需两次哈希(先定位 Segment,再定位桶),开销略大 - 内存占用高:每个 Segment 都是独立对象,含锁、计数器等冗余字段
- 无法很好支持红黑树转换、批量操作等新特性
新结构:Node 数组 + CAS + synchronized 锁单个桶
JDK 8 使用一个普通的 Node<k>[]</k> 数组(类似 HashMap),并发控制下沉到**数组槽位(bin)级别**:
-
CAS 用于无锁读写:如
put初始化桶、size计数、tabAt/casTabAt等底层操作都基于Unsafe的 CAS 指令 -
synchronized 锁单个 Node 链表头或 TreeBin:当需要修改某桶(如插入、删除、树化)时,只对该桶的首节点(或封装后的
TreeBin)加synchronized锁,其他桶完全不受影响 - 链表转红黑树时,由
TreeBin对象统一管理锁和读写状态,避免多线程反复竞争
关键操作如何体现新机制?
以 putVal 为例(简化逻辑):
立即学习“Java免费学习笔记(深入)”;
- 先通过
spread(key.hashCode()) & (n-1)定位桶索引 - 若桶为空,尝试 CAS 插入新
Node;失败则说明有竞争,进入下一轮 - 若桶首节点为
MOVED(正在扩容),协助扩容 - 否则,对桶首节点加
synchronized锁,遍历链表/红黑树执行插入或更新 - 插入后若链表长度 ≥ 8,且数组长度 ≥ 64,则触发树化(转为红黑树)
扩容过程也无全局锁
扩容不再是“一次全量复制”,而是采用分段迁移 + 协助扩容机制:
- 首个触发扩容的线程初始化
nextTable,并设置扩容戳(transferIndex) - 后续线程检测到扩容中,会主动帮助迁移未处理的桶区间
- 每个桶迁移时,仍只锁当前桶,迁移完将原桶设为
FWD节点,引导后续 get/put 转向新表 - 全程无全局锁,也不阻塞读操作(
get可在旧表或新表中查到数据)
不复杂但容易忽略:这种设计让 ConcurrentHashMap 在高并发下锁冲突大幅降低,吞吐量显著优于 JDK 7,同时保持了与 HashMap 接近的内存布局和操作语义。


















