软件技术 · 知识点深化 · 并发编程
锁与 synchronized:从偏向锁到锁升级的完整链路
synchronized 关键字看起来简单,背后却是一整套锁优化:偏向锁→轻量级锁→重量级锁。JVM 会根据竞争程度自动升级,从不降级。这一页把锁对象头、Monitor、自旋、ReentrantLock 对比一次讲清,面试不再只背"加锁"两个字。
① 怎么学(4 步走,约 70 分钟)
先建立直觉再抠细节,按这四步走最稳:
1看图建立直觉(10 分钟)
读②③:先在脑子里画出本课核心结构图。
2记完整体系(15 分钟)
读④:把对比表和公式看懂,不要急着背。
3跟例题走一遍(20 分钟)
精读⑤:看三个例题怎么用知识点解题。
4刷题纠错(剩余时间)
做⑦⑩,错题回⑥诊断。
本课小目标学完你要能:① 说出 synchronized 三种用法;② 解释锁升级过程;③ 对比 synchronized 与 ReentrantLock;④ 说清对象头 Mark Word 结构。
② 一图看懂:锁升级与锁对比全景
读法:中心是本课主题,四条分支展开核心维度,下方是典型应用与高频易错点。
③ 本质直觉:锁就是"门口排队证"
想象一个厕所只有一个坑位。没人抢的时候,进去的人在门上贴个名字条(偏向锁),下次还是他来直接进,不用反复确认。
偶尔有人来碰一下,发现名字不是自己,就把名字条撕掉,改成一把自行车锁(轻量级锁):大家轮流用手去抠锁,抠到就进,抠不到就在原地蹦跶几下(自旋),不真的去睡觉。
如果抢的人越来越多,自行车锁抠不过来,直接换成铁门大锁(重量级锁):抢不到的人去等候室排队睡觉(操作系统阻塞),等里面的人出来叫醒一个。这就是 Monitor。
锁只能升级不能降级:JVM 一旦发现竞争激烈,就升级到重量级,不会因为后来没人抢了再退回去。这是因为"降级"需要协调成本,不如直接维持现状。
synchronized 锁住的是什么实例方法锁 this;静态方法锁 Class 对象;代码块锁括号里指定的对象。锁的不是代码,是对象。
④ 完整知识体系
三种锁状态与 ReentrantLock 对比
| 对比项 | synchronized | ReentrantLock |
| 实现层级 | JVM 关键字 | JDK API(AQS) |
| 锁释放 | 自动释放 | 必须 finally 手动 unlock |
| 可中断 | 不可 | lockInterruptibly 可中断 |
| 可超时 | 否 | tryLock 带超时 |
| 公平锁 | 非公平 | 可选公平/非公平 |
| 条件队列 | 一个 waitSet | 多个 Condition |
| 锁升级 | 偏向→轻量→重量 | 直接基于 AQS 队列 |
对象头 Mark Word(32位)unused:25 | hashCode:31 | 分代:4 | 锁标志:2
重入次数偏向锁记录线程ID;轻量级锁在栈帧Lock Record计数;重量级锁在Monitor中计数
synchronized 三种用法
修饰实例方法:锁当前对象 this。
修饰静态方法:锁类对象 Xxx.class。
修饰代码块:锁括号里指定的对象。
Monitor(管程)结构
每个对象关联一个 Monitor:_owner 指向持有线程,_EntryList 存放等待锁的线程队列,_WaitSet 存放 wait() 等待的线程。进入时 owner 为空就设为自己,否则进 EntryList 阻塞。
为什么 wait() 必须在 synchronized 里wait/notify 操作的是 Monitor 的 WaitSet,没有持有 Monitor 就没有 WaitSet 可用,JVM 直接抛 IllegalMonitorStateException。
⑤ 应用场景与例题
例1 synchronized 锁的是 this 还是 Class?
两个线程分别调用同一个对象的实例 synchronized 方法和另一个对象的静态 synchronized 方法,会互斥吗?
不会。实例方法锁 this(对象锁),静态方法锁 Class(类锁),两把不同的锁互不影响。
例2 wait() 和 sleep() 区别?
为什么 wait 会释放锁,sleep 不释放?
wait 是 Object 方法,主动放弃 Monitor 进入 WaitSet,所以释放锁;sleep 是 Thread 静态方法,只是让线程暂停,不释放任何监视器。
例3 ReentrantLock 为什么要 finally unlock?
不写 finally 会怎样?
try 块里抛异常后,lock 不会自动释放,其他线程永远阻塞。必须在 finally 里 unlock(),确保异常路径也释放。
做题心法"锁的不是代码是对象",这句话能解决一半面试题。
⑥ 高频错误诊断(4 条)
错误1:以为 synchronized 锁的是代码块锁的是括号里的对象。不同对象两把锁,不互斥。
错误2:wait() 没在 synchronized 里调用直接抛 IllegalMonitorStateException,必须先持有对象锁。
错误3:ReentrantLock 忘记 finally unlock异常后锁泄漏,其他线程永久阻塞。
错误4:以为锁可以降级JVM 锁只升级不降级,一旦到重量级就维持,不会因竞争消失而退回偏向。
⑦ 考点真题演练(5 题)
考点分布
| 考法 | 出题形式 | 应对 |
| 锁升级 | 问顺序 | 偏向→轻量→重量 |
| 锁对象 | 问静态/实例方法 | 类锁 vs 对象锁 |
| wait/sleep | 谁释放锁 | wait 释放,sleep 不释放 |
| ReentrantLock | 比 synchronized 多什么 | 可中断/可超时/多条件 |
真题basic1. synchronized 修饰实例方法时,锁的是?
真题mid2. JVM 锁升级的正确顺序是?
真题mid3. 关于 wait() 和 sleep(),正确的是?
真题hard4. ReentrantLock 相比 synchronized,不具备以下哪个特性?
真题hard5. synchronized 静态方法和实例方法是否互斥?
⑧ 必背知识点卡
三用法:实例方法锁 this,静态方法锁 Class,代码块锁指定对象
升级:偏向→轻量→重量,只升不降
偏向锁:对象头记录线程ID,无竞争时零成本
轻量级:CAS 自旋,适合短任务
重量级:Monitor,EntryList 排队阻塞
wait/notify:必须在 synchronized 内,wait 释放锁
ReentrantLock:finally 必须 unlock,可中断可超时
⑨ 动手输出:解释为什么双重检查单例要 volatile
场景:写一个懒加载单例,为什么 instance = new Singleton() 要加 volatile?
① 现象:不加 volatile 时,可能拿到半初始化的对象。
② 原因:new 操作分三步:分配内存→初始化对象→引用指向内存。JIT 可能重排成 1→3→2。
③ 问题:线程 A 执行到 3(引用已非空但对象未初始化),线程 B 判断 instance != null 直接返回,拿到半成品。
④ 解法:volatile 禁止指令重排,保证初始化完成后引用才可见。
⑤ 配套:synchronized 保证同一时刻只有一个线程进临界区,volatile 保证可见性和有序性。
口述思路"锁解决互斥,volatile解决可见性和有序性,两个一起才是完整方案。"
⑩ 分层练习(基础 + 中档 + 拔高)
▍基础 6 题
基础1synchronized 修饰静态方法锁的是谁?
类对象 Xxx.class。
基础2wait() 在哪个类上定义?
java.lang.Object,所有对象都有。
基础3锁升级是单向还是双向?
单向,只升级不降级。
基础4ReentrantLock 不释放会怎样?
其他线程永久阻塞,必须 finally unlock。
基础5偏向锁记录的是什么?
最近获得锁的线程 ID。
基础6notify() 和 notifyAll() 区别?
notify 随机唤醒一个,notifyAll 唤醒全部。
▍中档 5 题
中档1为什么 wait() 要在 synchronized 里?
操作 Monitor 的 WaitSet 必须先持有 Monitor,否则 IllegalMonitorState。
中档2轻量级锁适合什么场景?
线程交替执行、持有时间短,自旋成本低于阻塞。
中档3重量级锁的缺点?
线程进入 OS 阻塞态,上下文切换开销大。
中档4公平锁和非公平锁区别?
公平锁按排队顺序;非公平锁允许新来的线程插队抢锁(吞吐高)。
中档5synchronized 锁升级发生在哪个阶段?
运行时根据竞争情况自动升级,无需手动指定。
▍拔高 5 题
拔高1对象头 Mark Word 存了什么?
hashCode、分代年龄、锁标志位、指向 Monitor 的指针等。
拔高2为什么 JDK15 废弃偏向锁?
现代 JVM 竞争普遍,偏向锁撤销成本高于收益。
拔高3AQS 是什么?
AbstractQueuedSynchronizer,ReentrantLock 等同步器的底层框架,用 CLH 队列管理等待线程。
拔高4Condition 的作用?
替代 wait/notify,一个锁可建多个条件队列(如生产者/消费者分离)。
拔高5synchronized 和 Lock 怎么选?
简单同步用 synchronized(JVM 优化);需要可中断/超时/多条件时用 ReentrantLock。
⑪ 记忆口诀 + 7 天复习计划
三句口诀① 锁对象不锁代码,实例 this 静态 Class。② 偏向轻量重量升,只升不降记心中。③ wait 放锁 sleep 不放,ReentrantLock 必 finally。
| 天 | 任务 | 自检 |
| 第 1 天 | 读②③,画锁升级流程图 | 三种锁状态说清 |
| 第 2 天 | 背 synchronized 用法 + 基础 6 题 | 三种用法默写 |
| 第 3 天 | 做中档 5 题,对比 wait/sleep | 区别能讲 |
| 第 4 天 | 做拔高 5 题,写双重检查单例 | 代码正确 |
| 第 5 天 | 做⑦真题 5 题 | 限时每题 2 分钟 |
| 第 6-7 天 | 合书口述 Mark Word 和 Monitor | 不看资料 |
← 返回软件技术总览