← 返回软件技术总览 软件技术 · 知识点深化 · 锁与 synchronized:从偏向锁到锁升级的完整链路
软件技术 · 知识点深化 · 并发编程

锁与 synchronized:从偏向锁到锁升级的完整链路

synchronized 关键字看起来简单,背后却是一整套锁优化:偏向锁→轻量级锁→重量级锁。JVM 会根据竞争程度自动升级,从不降级。这一页把锁对象头、Monitor、自旋、ReentrantLock 对比一次讲清,面试不再只背"加锁"两个字。

① 怎么学(4 步走,约 70 分钟)

先建立直觉再抠细节,按这四步走最稳:

1看图建立直觉(10 分钟)
读②③:先在脑子里画出本课核心结构图。
2记完整体系(15 分钟)
读④:把对比表和公式看懂,不要急着背。
3跟例题走一遍(20 分钟)
精读⑤:看三个例题怎么用知识点解题。
4刷题纠错(剩余时间)
做⑦⑩,错题回⑥诊断。
本课小目标学完你要能:① 说出 synchronized 三种用法;② 解释锁升级过程;③ 对比 synchronized 与 ReentrantLock;④ 说清对象头 Mark Word 结构。

② 一图看懂:锁升级与锁对比全景

锁 偏向锁 无竞争时记录线程ID 轻量级锁 交替CAS自旋 重量级锁 Monitor阻塞 ReentrantLock 可中断可超时 应用:单例/计数器/并发容器 任何共享资源保护 易错:锁粒度过大拖慢并发 锁住整个方法而非代码块
读法:中心是本课主题,四条分支展开核心维度,下方是典型应用与高频易错点。

③ 本质直觉:锁就是"门口排队证"

想象一个厕所只有一个坑位。没人抢的时候,进去的人在门上贴个名字条(偏向锁),下次还是他来直接进,不用反复确认。

偶尔有人来碰一下,发现名字不是自己,就把名字条撕掉,改成一把自行车锁(轻量级锁):大家轮流用手去抠锁,抠到就进,抠不到就在原地蹦跶几下(自旋),不真的去睡觉。

如果抢的人越来越多,自行车锁抠不过来,直接换成铁门大锁(重量级锁):抢不到的人去等候室排队睡觉(操作系统阻塞),等里面的人出来叫醒一个。这就是 Monitor。

锁只能升级不能降级:JVM 一旦发现竞争激烈,就升级到重量级,不会因为后来没人抢了再退回去。这是因为"降级"需要协调成本,不如直接维持现状。

偏向锁记录线程ID 轻量级锁CAS自旋 重量级锁Monitor阻塞 竞争加剧 → 对象头 Mark Word25bit hashCode + 4bit 分代 + 2bit 锁标志
synchronized 锁住的是什么实例方法锁 this;静态方法锁 Class 对象;代码块锁括号里指定的对象。锁的不是代码,是对象。

④ 完整知识体系

三种锁状态与 ReentrantLock 对比

对比项synchronizedReentrantLock
实现层级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不看资料

← 返回软件技术总览