concurrenthashmap在jdk 8+中不全局加锁,而是采用cas无锁插入空桶、synchronized仅锁冲突桶头节点、扩容时协助迁移的细粒度并发策略。

它的核心思想是:**分段无锁 + CAS + 小范围同步(synchronized 块)**。具体加锁逻辑发生在底层桶(bin)级别,且仅在必要时、针对单个链表头或红黑树根节点加锁。
1. 不加锁的主路径:CAS 尝试插入
大多数情况下,`putVal` 会先尝试用 CAS(Compare-And-Swap) 直接更新数组对应位置的节点(即 `tab[i]`):
- 如果该桶为空(`tab[i] == null`),直接 CAS 设置为新节点;成功则返回,全程无锁。
- 如果 CAS 失败(说明有其他线程已修改),才进入后续处理逻辑。
2. 需要加锁的两种典型场景
只有当发生哈希冲突(桶非空),且需要修改链表或树结构时,才会对**当前桶的首节点(first node)加 `synchronized` 锁**:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 链表插入/遍历:若桶是普通 Node 链表,`putVal` 会 `synchronized (f)`(其中 `f = tab[i]`),然后遍历链表查找 key,决定是更新还是尾插。
- 红黑树操作:若桶是 TreeBin(包装了红黑树),同样 `synchronized (f)`,再调用 `putTreeVal` 安全插入树中。
- 注意:这个锁只作用于当前桶的头节点对象(一个普通 Object),不是全局锁,不同桶之间完全不互斥。
3. 扩容时的特殊协调机制
扩容(resize)过程中,`putVal` 可能遇到正在迁移的桶(标记为 `ForwardingNode`):
- 此时不会加锁,而是协助扩容(调用 `helpTransfer`)。
- 协助过程本身也通过 CAS 修改扩容指标和迁移状态,仅在操作某个迁移任务段时,才对对应桶加 `synchronized` 锁(同上,粒度仍是单桶)。
4. 为什么不用 ReentrantLock 或 synchronized 方法?
这是设计取舍的结果:
- `synchronized` 方法会锁住整个 map 实例,严重串行化,违背高并发初衷。
- `ReentrantLock` 虽可重入,但维护大量锁对象开销大,且难以实现细粒度控制。
- 当前方案:每个桶独立锁对象(头节点本身)、CAS 快路径、扩容协作 —— 在吞吐量、内存、复杂度间取得平衡。
简单说:ConcurrentHashMap 的“加锁”是懒加载、按需、极细粒度的——只在真正要改某条链表或某棵树时,才锁住那个桶的头节点,而且只锁那一小段代码块。这不是“给 putVal 加锁”,而是“在 putVal 的局部关键区,对局部资源加局部锁”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










