文章目录
-
- [1. 锁对象的演进:从"大锁"到"小锁"](#1. 锁对象的演进:从“大锁”到“小锁”)
- [2. 为什么不全用原子操作(CAS)?](#2. 为什么不全用原子操作(CAS)?)
- [3. 为什么选 `synchronized` 而不是 `ReentrantLock`?](#3. 为什么选
synchronized而不是ReentrantLock?) -
- [① 内置锁的"主场优势" (JVM 优化)](#① 内置锁的“主场优势” (JVM 优化))
- [② 内存开销的极致压榨](#② 内存开销的极致压榨)
- [4. 总结:现在的协作逻辑](#4. 总结:现在的协作逻辑)
- 核心结论
这是一个快手的面试题,当时我被问到了
jdk1.7采用了segment+ReentranceLock
jdk1.8采用CAS+synchronized处理竞态
这是一个非常经典且能体现 Java 性能演进的问题。
其实 ConcurrentHashMap 并不是完全放弃了原子操作。准确地说,它现在的设计策略是:先用原子操作(CAS),万不得已才动用 synchronized。
在 Java 8 之后,它的这种转变核心逻辑是为了在极细粒度 的锁竞争下,压榨出比 ReentrantLock 更高的性能。我们可以从以下几个维度来系统分析:
1. 锁对象的演进:从"大锁"到"小锁"
- Java 7(Segment + ReentrantLock) :
它把 Map 分成了 16 个 Segment,每个 Segment 都是一个ReentrantLock。这意味着如果你有两个不同的 Key 落在同一个 Segment 里,哪怕它们在不同的桶(Bucket)上,也得竞争同一把锁。 - Java 8+(Node + synchronized) :
它取消了 Segment,直接锁住每个桶的第一个节点(Node) 。- CAS 负责"无中生有" :如果桶是空的,直接通过
CAS插入,完全不加锁。 - synchronized 负责"精细管理" :只有当桶里已经有数据(发生碰撞)时,才针对这一个桶的头节点加
synchronized。
- CAS 负责"无中生有" :如果桶是空的,直接通过
2. 为什么不全用原子操作(CAS)?
原子操作(如 compareAndSet)虽然快,但它只能保证单个变量修改的原子性。
- 痛点:当发生哈希冲突时,我们需要在链表或红黑树上进行一系列复杂操作(比如遍历、比较、插入、平衡旋转)。
- 局限性:要把这一整套"组合拳"全部封装进一个 CAS 里,逻辑会变得极其复杂且难以维护,甚至会因为频繁的自旋(Spinning)导致 CPU 空转,浪费大量资源。此时,使用锁来保护整个代码块是更稳妥、更高效的选择。
3. 为什么选 synchronized 而不是 ReentrantLock?
这是很多人的误区,认为 synchronized 慢。但在 Java 8 及以后的版本中,情况发生了反转:
① 内置锁的"主场优势" (JVM 优化)
synchronized 是 JVM 的亲儿子,拥有各种"黑科技"加持:
- 锁升级机制 :它会经历 偏向锁 -> 轻量级锁 -> 重量级锁 的动态演进。在竞争不激烈时,它的开销极低。
- 锁消除与锁粗化 :JIT 编译器能根据代码运行情况,自动把没必要的锁删掉,或者把多个小锁合并。这些是作为第三方类库的
ReentrantLock很难做到的。
② 内存开销的极致压榨
ReentrantLock是一个对象,它需要维护AQS同步队列、锁状态等一系列成员变量。- 如果 Map 有 1024 个桶,全用
ReentrantLock就要创建 1024 个锁对象,这非常浪费内存。 - 而
synchronized直接利用对象头(Mark Word)里的位来标记锁状态,不需要额外创建锁对象 。对于ConcurrentHashMap这种动辄成千上万个桶的结构来说,内存节省非常可观。
最好看一下下面这三篇我写的文章,可以更好的理解
为什么不逃逸代表不需要锁,JIT会直接删掉锁
什么是逃逸分析
锁消除和锁粗化
4. 总结:现在的协作逻辑
在 ConcurrentHashMap 的源码里,一次 put 操作的逻辑是这样的:
- 计算哈希值。
- 如果是空桶 :尝试用 CAS 插入。成功则返回。
- 如果正在扩容 :当前线程去协助搬迁(Help Transfer)。
- 如果是旧桶冲突 :使用
synchronized锁住头节点,进行链表或红黑树的操作。
核心结论
ConcurrentHashMap 采用 synchronized 的核心痛点不是为了代替原子操作,而是为了在原子操作覆盖不到的复杂逻辑区(链表/树操作),提供一种内存更省、底层优化更强、且粒度更细的同步机制。
现在的 synchronized 已经不是当年那个笨重的"重量级锁"了,它在低竞争场景下的性能表现,已经足以让它在集合框架中重新坐稳"一哥"的位置。