孤舟笔记 并发篇十二 Java并发锁这么多怎么分?一张图理清乐观锁悲观锁公平锁可重入锁的关系
文章目录
- 先说结论:锁的分类维度
- 核心理解:同一把锁可以有多个"身份"
- 维度一:加锁策略——乐观 vs 悲观
- 维度二:公平性——公平 vs 非公平
- 维度三:重入性——可重入 vs 不可重入
- 维度四:排他性——独占 vs 共享
- 维度五:等待方式——自旋 vs 阻塞
- 维度六:synchronized 的 JVM 优化级别
- 并发锁分类全景
- 回答技巧与点评
- 标准回答
- 加分回答
- 面试官点评
个人网站
学 Java 并发的时候,锁的名字像撒豆子一样蹦出来:乐观锁、悲观锁、公平锁、非公平锁、可重入锁、独占锁、共享锁、自旋锁、偏向锁、轻量级锁……你越学越晕,感觉每把锁都是独立的,根本串不起来。
其实,这些锁不是并列关系,而是从不同维度给同一把锁起的名字。今天咱们就把这团乱麻理清楚。
先说结论:锁的分类维度
||| 分类维度 | 锁类型 | 说明 |
|||------|------|------|
||| 加锁策略 | 乐观锁 / 悲观锁 | 加锁前是否假设冲突 |
||| 公平性 | 公平锁 / 非公平锁 | 是否按等待顺序获取 |
||| 重入性 | 可重入锁 / 不可重入锁 | 同一线程能否重复获取 |
||| 排他性 | 独占锁(排他锁)/ 共享锁 | 是否只允许一个线程持有 |
||| 等待方式 | 自旋锁 / 阻塞锁 | 等锁时是自旋还是挂起 |
||| 锁粒度 | 偏向锁 / 轻量级锁 / 重量级锁 | synchronized 的 JVM 优化级别 |
一句话记住:锁的分类就像给人分类——按性别分、按年龄分、按职业分,同一个人可以同时属于多个分类。
核心理解:同一把锁可以有多个"身份"
这是最关键的一点——一把锁可以同时属于多个分类维度。
比如ReentrantLock:
- 它是悲观锁(加锁才访问)
- 它是可重入锁(同线程可重复获取,所以叫 Reentrant)
- 它可以是公平锁或非公平锁(构造参数决定)
- 它是独占锁(同一时刻只有一个线程持有)
- 它等待时是阻塞锁(park 挂起,不是自旋)
就像一个人可以同时是"男生、90后、程序员"——这些标签不冲突,只是分类维度不同。
生活类比:你去买车,销售说"这是自动挡、SUV、白色、七座"。自动挡 vs 手动挡是一个维度,SUV vs 轿车是另一个维度,颜色是第三个维度——它们描述的是同一辆车的不同方面。
维度一:加锁策略——乐观 vs 悲观
这是思想层面的分类:
- 悲观锁:先锁再说,认为冲突一定发生 →
synchronized、ReentrantLock - 乐观锁:先干后验,认为冲突概率低 → CAS、版本号
这个维度和后面的维度完全正交。一把悲观锁可以同时是公平锁、可重入锁、独占锁——不矛盾。
维度二:公平性——公平 vs 非公平
这是排队策略的分类:
- 公平锁:先来先得,按等待顺序获取 →
new ReentrantLock(true) - 非公平锁:允许插队,直接 CAS 抢 →
new ReentrantLock(false)(默认)
只有涉及"排队"的锁才有公平性概念。乐观锁(CAS)不存在排队,所以没有公平/非公平的说法。
维度三:重入性——可重入 vs 不可重入
这是同线程行为的分类:
- 可重入锁:同线程可重复获取同一把锁 →
synchronized、ReentrantLock - 不可重入锁:同线程重入会死锁 → 实际很少使用
Java 中主流锁基本都是可重入的,不可重入锁属于特殊场景(如某些自旋锁实现)。
维度四:排他性——独占 vs 共享
这是多线程行为的分类:
- 独占锁(排他锁):同一时刻只有一个线程持有 →
synchronized、ReentrantLock - 共享锁:同一时刻多个线程可同时持有 →
ReentrantReadWriteLock.ReadLock、Semaphore
ReentrantReadWriteLock最有意思——读锁是共享锁,写锁是独占锁。读读不互斥,读写互斥,写写互斥。
维度五:等待方式——自旋 vs 阻塞
这是等锁行为的分类:
- 自旋锁:等锁时 CPU 空转,不释放时间片 → CAS 自旋
- 阻塞锁:等锁时线程挂起(park),让出 CPU →
synchronized(重量级锁)、ReentrantLock
自旋锁适合锁持有时间极短的场景——等一小会儿就拿到了,比挂起再唤醒快。但如果锁持有时间长,自旋就是白白浪费 CPU。
synchronized的轻量级锁阶段就是自旋,膨胀为重量级锁后变为阻塞。所以synchronized其实两种都用了。
维度六:synchronized 的 JVM 优化级别
这是synchronized独有的锁升级过程:
- 偏向锁:只有一个线程访问,连 CAS 都不做,直接标记
- 轻量级锁:短时间竞争,CAS 自旋
- 重量级锁:竞争激烈,操作系统互斥量,线程阻塞
锁只能升级不能降级(严格说偏向锁可以批量撤销)。这是 JVM 帮你自动做的优化,代码层面无感知。
并发锁分类全景
Java 并发锁分类 全景 按加锁策略 ├── 乐观锁 ── CAS、版本号 └── 悲观锁 ── synchronized、ReentrantLock 按公平性 ├── 公平锁 ── ReentrantLock(true) └── 非公平锁 ── ReentrantLock(false)、synchronized 按重入性 ├── 可重入锁 ── synchronized、ReentrantLock └── 不可重入锁 ── 极少使用 按排他性 ├── 独占锁 ── synchronized、ReentrantLock、WriteLock └── 共享锁 ── ReadLock、Semaphore、CountDownLatch 按等待方式 ├── 自旋锁 ── CAS 自旋 └── 阻塞锁 ── synchronized(重量级)、ReentrantLock synchronized 锁升级 偏向锁 → 轻量级锁(自旋) → 重量级锁(阻塞) 关键理解 一把锁可同时属于多个维度,维度之间正交不互斥 口诀:乐观悲观加锁策,公平排队谁先得, 重入自旋独占共,维度正交要分清。回答技巧与点评
标准回答
Java 并发锁可以从多个维度分类:按加锁策略分为乐观锁和悲观锁;按公平性分为公平锁和非公平锁;按重入性分为可重入锁和不可重入锁;按排他性分为独占锁和共享锁;按等待方式分为自旋锁和阻塞锁。这些维度是正交的,同一把锁可以同时属于多个维度。比如 ReentrantLock 既是悲观锁、可重入锁、独占锁,又可以是公平锁或非公平锁。
加分回答
- 设计思想:锁的分类本质是"关注点分离"——每个维度解决一个特定问题。乐观/悲观解决"要不要加锁",公平/非公平解决"排队策略",独占/共享解决"并发度"。理解维度而非记名字,才能真正选对锁
- 边界情况:有些锁的组合是不存在的。比如乐观锁没有公平/非公平之分(CAS 没有排队),不可重入的悲观锁在 Java 中极少使用(会自我死锁)。synchronized 的锁升级是不可逆的(只升不降)
- 实际应用:选锁时按维度逐个决策——先选乐观/悲观(看竞争程度),再选公平/非公平(看是否需要严格顺序),再选独占/共享(看是否允许多线程同时读),最后选等待方式(看锁持有时间长短)
面试官点评
这道题考的是你对锁的体系化认知。把所有锁名罗列一遍只能拿基础分。能说清"同一把锁可以属于多个分类维度"这个核心认知,逐维度展开,才是面试官想听的。如果你能现场画出分类图,并指出哪些组合不存在,面试官会非常认可。
原文阅读
内容有帮助?点赞、收藏、关注三连!评论区等你 💪
