jdk 1.8 concurrenthashmap采用cas与synchronized协同机制:cas用于空桶插入、初始化及扩容标记等无锁场景;synchronized仅锁定冲突桶的头节点,处理链表/红黑树操作;二者按优先级协作,先cas后加锁,兼顾性能与安全。

ConcurrentHashMap 在 JDK 1.8 中不再使用分段锁,而是用 CAS + synchronized 协同保障线程安全。这种组合不是简单“替换”,而是按场景分工:能无锁就无锁,必须加锁时只锁最窄范围。
什么时候用 CAS?
CAS 主要用于低竞争、单点原子写入的场景,开销极小且完全不阻塞其他线程:
- 桶(数组槽位)为空时插入新节点:直接调用
CASTabAt尝试写入,成功即返回 - 初始化哈希表数组:多个线程竞争时,仅一个能通过 CAS 设置
sizeCtl完成初始化 - 扩容控制标记设置:如将某桶设为
ForwardingNode(表示正在迁移),靠 CAS 保证唯一性
什么时候用 synchronized?
synchronized 不是对整个 map 加锁,而是精准锁定发生哈希冲突的桶头节点(Node 对象本身):
- 桶非空、需遍历链表或红黑树插入/更新时,对头节点加锁
- 链表转红黑树、树节点插入或删除等结构变更操作
- computeIfAbsent 等复合操作内部,也基于该桶头节点同步执行
注意:锁对象是 Node 实例,不是额外创建的 Lock 对象,零内存冗余;且因竞争极少,绝大多数情况下停留在偏向锁或轻量级锁状态,性能损耗极低。
CAS 和 synchronized 怎么配合?
两者不是并列选择,而是一套有优先级的协作流程:
- 先尝试 CAS —— 成功则全程无锁,最快路径
- CAS 失败(比如桶已被其他线程填满),再检查是否为
ForwardingNode(扩容中):是则协助迁移,不是则对头节点加 synchronized 执行后续逻辑 - 扩容期间,get 可能跨新旧表读取,但所有写操作都会先判断迁移状态,确保不破坏一致性
为什么不用 ReentrantLock 而选 synchronized?
这不是降级,而是更贴合场景的优化:
- 内存更省:每个桶复用 Node 对象头里的 Monitor,无需为每个桶分配 ReentrantLock 实例
- 性能更好:在短临界区、低竞争下,JDK 1.6+ 的 synchronized 已远超 ReentrantLock 启动开销
- 语义更稳:synchronized 是 JVM 层保障,不会漏 unlock;ReentrantLock 需手动配对,易出错
- 功能刚好够用:不需要公平锁、条件队列、可中断等高级特性
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











