jdk 8 的 concurrenthashmap 已移除分段锁(segment),改用 synchronized 锁首节点 + cas + 红黑树;segment 仅存在于 jdk 7 及更早版本,其 16 段设计存在负载不均、size() 不准、扩容粗粒度等问题。

ConcurrentHashMap 的分段锁在 JDK 8 中已经不存在了
Java 8 的 ConcurrentHashMap 彻底移除了分段锁(Segment)机制,改用 synchronized + CAS + 红黑树的组合方案。如果你在源码里还搜 Segment 类,它只存在于 JDK 7 及更早版本——JDK 8 起这个类已被删除。
所以,别再试图“复刻 JDK 7 的分段锁”来解决并发问题,那套设计在现代 JVM 上反而成了性能瓶颈和理解负担。
JDK 7 的 Segment 分段锁是怎么工作的
它把整个哈希表切分成 16 个(默认)独立的 Segment,每个 Segment 是一个可重入锁保护的 HashEntry 数组。写操作只锁住对应 key 的 Segment,理论上支持最多 16 个线程并发写不同段。
-
put()先通过hash & (ssize - 1)定位到具体Segment,再加锁操作其内部数组 -
size()需要尝试两次无锁统计,失败后才逐个锁住所有Segment—— 这是它最常被吐槽的“不精确又慢”的地方 - 扩容是单个
Segment自行完成的,但锁粒度仍比 JDK 8 的 Node 级别粗得多
JDK 8 怎么做到比分段锁还快
核心是把锁进一步下放到桶(Node)级别,并用 synchronized 锁住首节点,配合 CAS 做无锁插入。当链表长度 ≥ 8 且 table 长度 ≥ 64 时,会转为红黑树,避免哈希碰撞导致的遍历退化。
立即学习“Java免费学习笔记(深入)”;
关键点:
- 定位桶用
(n - 1) & hash,和 HashMap 一致;锁对象是该桶的first节点(Node实例),不是全局或段级对象 -
putVal()中,先无锁 CAS 尝试插入头结点;失败再加锁重试,避免多数场景下进入临界区 - 扩容时多个线程可协作迁移(
transfer()),每个线程负责一部分桶,不需要锁整个 table 或 segment -
TreeBin是红黑树的包装节点,它自己才是锁对象 —— 所以树操作也是细粒度的
如果真要手写分段锁,注意这几点
除非你在维护遗留系统或做教学演示,否则不建议手动实现。但若必须写,要注意:
- 分段数不宜硬编码为 16:应根据预期并发数和 CPU 核心数权衡,比如
Runtime.getRuntime().availableProcessors() * 2 - 每个段的锁不能是
new Object()后长期持有,得确保哈希分布均匀,否则会出现“热点段”——某个Segment被频繁争用,其他段空闲 - 段内仍需处理链表/树的并发安全,不能只靠段锁就认为内部结构天然线程安全
- JVM 对
synchronized的优化(偏向锁、轻量锁、锁膨胀)在段锁场景下效果有限,因为锁对象太多,容易快速升级为重量级锁
真正难的从来不是“怎么分段”,而是“怎么让段之间负载均衡 + 扩容时不阻塞 + 迭代时不出现 ConcurrentModificationException”。JDK 8 的方案之所以胜出,是因为它把锁、CAS、结构演进全揉进了同一个原子操作路径里,而不是靠切分来掩盖复杂性。










