第一章 常见锁策略(面试高频基础)
核心定位:这是锁的设计思想与分类维度,不局限于Java语言,是所有并发锁的底层逻辑。 ⚠️ 面试必记:这6组是6个独立的分类角度,不是6种锁。同一个锁(比如
synchronized)可以同时属于多个分类。
1. 悲观锁 vs 乐观锁(冲突预判维度,面试第一问)
悲观锁
核心思想:默认一定会发生并发冲突,每次操作前先上锁,拿不到锁就阻塞等待。
- 通俗例子(PDF原例):同学A找老师问问题,默认老师很忙。先发消息预约(加锁),老师同意了才过去问;老师忙就下次再约。
- 代表实现:
synchronized、ReentrantLock、数据库select ... for update - 特点:
- 严格互斥,线程安全
- 拿不到锁就阻塞,线程挂起
- 适用场景:并发冲突多、写操作多的场景
乐观锁
核心思想:默认不会发生并发冲突,不上锁直接操作;提交更新时才校验是否冲突。冲突则返回失败,由业务层决定重试或报错。
- 通俗例子(PDF原例):同学B找老师问问题,默认老师很闲。直接过去找老师;老师有空就问完,老师忙就直接走,下次再来。
- 代表实现:CAS机制(Atomic原子类)、数据库版本号机制
- 特点:
- 不加锁,无阻塞,并发度高
- 冲突频繁时,反复重试会消耗CPU
- 适用场景:并发冲突少、读操作多的场景
✨ 面试补充
synchronized 不是固定的悲观/乐观:
- 初始竞争少:偏向乐观思路,尽量不加锁
- 检测到锁竞争频繁:自动切换为悲观锁策略
面试标准答法
问:谈谈你对乐观锁和悲观锁的理解? 答:悲观锁认为并发冲突概率高,操作前先加锁互斥,典型实现是synchronized;乐观锁认为冲突概率低,不加锁直接操作,提交时校验冲突,典型实现是CAS版本号机制。悲观锁适合写多冲突多的场景,乐观锁适合读多冲突少的场景。
2. 重量级锁 vs 轻量级锁(synchronized底层实现维度)
底层链路:CPU原子指令 → 操作系统mutex互斥锁 → JVM实现synchronized 区分核心:是否重度依赖操作系统内核的mutex互斥锁
重量级锁
核心:加锁重度依赖操作系统提供的mutex互斥锁。
- 过程:抢锁失败 → 操作系统把线程挂起休眠 → 锁释放后操作系统唤醒线程
- 代价:大量用户态 ↔ 内核态切换,触发线程调度,开销极高
- 触发场景:锁竞争激烈、锁持有时间长
轻量级锁
核心:尽量在用户态通过CAS完成加锁,不调用操作系统mutex;实在抢不到才升级为重量级锁。
- 过程:抢锁失败 → 短暂自旋重试,不挂起线程
- 代价:少量内核态切换,线程调度少,开销低
- 触发场景:多个线程交替使用锁,几乎不同时竞争
✨ 通俗理解
- 用户态:银行大厅自己能办的事,成本可控、速度快
- 内核态:必须到窗口交给工作人员办,成本不可控、速度慢
- 轻量级锁:尽量大厅自己解决,少找窗口
- 重量级锁:全程找窗口办理,来回排队开销大
面试考点
synchronized 锁升级:一开始是轻量级锁;锁冲突严重时,自动膨胀为重量级锁。锁只能升级,不能降级。
3. 自旋锁 vs 挂起等待锁(抢锁失败后的行为维度)
这是「抢锁失败时,线程怎么做」的两种策略
挂起等待锁(阻塞锁)
抢锁失败 → 线程直接挂起休眠,让出CPU资源;锁释放后由操作系统唤醒。
- 优点:不消耗CPU,锁持有时间长时更划算
- 缺点:线程挂起/唤醒需要内核态切换,响应慢、开销大
- 代表:synchronized重量级锁
自旋锁(Spin Lock)
抢锁失败 → 不放弃CPU,原地循环反复尝试抢锁,直到拿到锁为止。
- 伪代码
while (抢锁() == 失败) {
// 空转循环,持续重试
}
- 优点:锁很快释放时,无需线程切换,响应极快,效率高
- 缺点:锁持有时间长时,循环空转持续消耗CPU,锁冲突不是很激烈的时候高效
- 代表:synchronized轻量级锁底层、CAS实现的自定义锁
✨ 面试关联
自旋锁是轻量级锁的典型实现方式;synchronized的轻量级锁阶段,就是用自适应自旋锁实现的。
自适应自旋:不会无限自旋,达到一定次数/时间还拿不到锁,就停止自旋,升级为重量级锁。
通俗例子
- 挂起等待锁:表白被拒后彻底躺平,很久之后女神回头才再尝试
- 自旋锁:每天早安晚安持续刷存在感,女神一分手立刻上位
4. 公平锁 vs 非公平锁(锁分配顺序维度)
场景:线程A先拿到锁,线程B、C先后抢锁失败,进入等待;A释放锁。
公平锁
遵守先来后到,等待最久的线程优先拿到锁。
- 实现:需要额外的等待队列,记录线程的先后顺序
- 优点:不会饿死线程,顺序可控
- 缺点:需要维护队列,性能略低
非公平锁
不遵守先来后到,释放锁时,所有等待线程随机抢,谁抢到是谁的。
- 实现:操作系统原生线程调度就是随机的,默认就是非公平
- 优点:性能更高,不需要维护队列
- 缺点:可能出现线程饿死(某个线程一直抢不到)
✨ 面试考点
synchronized是非公平锁ReentrantLock默认非公平锁;构造方法传入true可开启公平锁
5. 可重入锁 vs 不可重入锁(同一个线程能否重复加锁维度)
不可重入锁
同一个线程,没释放锁的情况下再次加同一把锁,会直接阻塞自己,造成死锁。
- 例子:递归函数里加锁,第二次加锁就卡死自己
- 代表:Linux系统原生mutex互斥锁
可重入锁(递归锁)
允许同一个线程多次获取同一把锁,不会自己卡死自己。
- 实现原理:锁内部记录「持有锁的线程ID」+「加锁次数计数器」
- 同一个线程再次加锁:计数器+1,直接放行
- 解锁:计数器-1,计数器归0时锁才真正释放
- 代表:
synchronized、ReentrantLock(Java中所有标准锁都是可重入的)
面试考点
问:synchronized是可重入锁吗? 答:是。内部会记录持有线程和加锁次数,同一个线程多次加锁不会死锁,解锁次数和加锁次数匹配才会真正释放锁。
6. 读写锁(ReadWriteLock)(读写场景优化)
背景
普通互斥锁不管读还是写,一律互斥。但多线程同时读数据,不会有线程安全问题,全互斥会浪费性能。 读写锁就是为了优化「读多写少」场景诞生的。
三条核心规则
- 读锁 和 读锁:不互斥(多个线程可以同时读)
- 写锁 和 写锁:互斥
- 读锁 和 写锁:互斥(读的时候不能写,写的时候不能读)
代表实现
ReentrantReadWriteLock,包含读锁ReadLock和写锁WriteLock,都支持lock/unlock。
适用场景
频繁读、很少写的场景,比如配置信息、学生名册、商品详情查询。
例子:教务系统,每天几十上百次查学生/查作业(读),很久才修改一次名单/布置作业(写)。
面试考点
synchronized 不是读写锁,是普通互斥锁。
例子:synchronized 锁属性全总结(面试必背)
锁策略是从不同维度描述锁的特性,
synchronized不是单一类型的锁,多个维度都支持自适应升级。
| 分类维度 | synchronized 属性 | 详细说明 |
|---|---|---|
| 悲观锁 / 乐观锁 | 自适应 | 初始预测锁冲突概率低,以乐观锁思路运行;检测到冲突频繁后,自动升级为悲观锁。 |
| 重量级锁 / 轻量级锁 | 自适应 | 初始为轻量级锁(用户态CAS实现);锁冲突加剧后,膨胀为重量级锁(依赖操作系统mutex,内核态实现)。 |
| 自旋锁 / 挂起等待锁 | 自适应 | 轻量级锁阶段采用自适应自旋策略抢锁;升级为重量级锁后,抢锁失败线程进入挂起等待。 |
| 读写锁 / 普通互斥锁 | 不是读写锁 | 属于普通互斥锁,读操作、写操作全部互斥,不支持读读并发。 |
| 可重入锁 / 不可重入锁 | 可重入锁 | 允许同一个线程多次获取同一把锁;内部通过「持有线程ID + 加锁计数器」实现,不会自己锁死自己。 |
| 公平锁 / 非公平锁 | 非公平锁 | 不遵守先来后到规则,锁释放后所有等待线程随机竞争,可能出现线程饥饿。 |
锁升级完整流程(只能升级,不能降级)
- 无锁 → 偏向锁:只有一个线程使用锁时,仅在对象头做标记,避免加锁开销。
- 偏向锁 → 轻量级锁:出现第二个线程竞争锁,撤销偏向锁,升级为轻量级锁,通过CAS+自适应自旋抢锁。
- 轻量级锁 → 重量级锁:自旋多次仍抢不到锁、锁持有时间变长,锁膨胀为重量级锁,依赖操作系统mutex,抢锁失败线程挂起等待。
一句话速记
synchronized 自适应升级:偏→轻→重;可重入、非公平、不是读写锁。
📌 第一章 面试核心总结
- 按冲突预判分:悲观锁、乐观锁
- 按底层实现分:重量级锁、轻量级锁
- 按失败行为分:自旋锁、挂起等待锁
- 按分配顺序分:公平锁、非公平锁
- 按能否重入分:可重入锁、不可重入锁
- 按读写区分:普通互斥锁、读写锁
synchronized标签:悲观锁、可重入锁、非公平锁;从轻量级锁起步,竞争激烈升级为重量级锁;轻量级阶段采用自适应自旋。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)