主题
概念卡片:synchronized 锁升级与 Monitor
一句话机制
synchronized 的锁存在对象头 Mark Word 里,靠 Monitor(底层是 OS 的 Mutex Lock)实现互斥;JDK6 为减少加解锁开销引入「偏向锁→轻量级锁→重量级锁」四级升级,锁状态只升不降。 理解锁升级 = 理解"锁是拿 CPU 自旋换线程阻塞"的权衡过程。
两个前置概念
- Java 对象头 = Mark Word(存 hashCode/分代年龄/锁标志位/偏向线程 ID,随锁状态动态复用)+ Klass Pointer(类型指针,指向类元数据)
- Monitor:每个 Java 对象都有一把看不见的锁(内部锁/Monitor 锁),Monitor 里
Owner字段记录持锁线程;依赖 OS 的 Mutex Lock 实现
锁升级四级(只升不降)
无锁 → 偏向锁 → 轻量级锁 → 重量级锁| 状态 | 标志位 | 触发条件 | 加锁方式 | 代价 |
|---|---|---|---|---|
| 无锁 | 01 | 无竞争 | CAS(乐观锁) | 几乎无 |
| 偏向锁 | 01+偏向位 | 同线程反复加锁 | 仅 1 次 CAS 换 ThreadID | 极低 |
| 轻量级锁 | 00 | 有竞争但轻微 | CAS + 自旋(不阻塞) | 占 CPU |
| 重量级锁 | 10 | 自旋超限/多线程竞争 | OS Mutex,线程阻塞 | 上下文切换 |
各状态要点
- 偏向锁:一段同步代码总被同一线程访问时,Mark Word 里存锁偏向的线程 ID,进出同步块只检测 ID、不再 CAS。撤销偏向锁要等全局安全点(暂停持锁线程),撤销后回到无锁或轻量级锁。
- 轻量级锁:偏向锁被其他线程访问时升级。线程在栈帧里建 Lock Record 拷贝 Mark Word,再用 CAS 把对象头 Mark Word 换成指向 Lock Record 的指针。CAS 失败则判断「是否自己已持锁」(重入)还是「多线程竞争」(升级重量级)。
- 重量级锁:自旋超过一定次数,或「一持锁 + 一自旋 + 第三方来访」时升级,等待线程全部阻塞。
为什么不直接都用重量级锁:阻塞/唤醒线程要 OS 切换 CPU 状态、耗 CPU 时间,若同步块内容极简单,状态切换耗时可能比用户代码还长——这就是 JDK6 之前 synchronized 慢的根因,也是引入偏向锁/轻量级锁的动机。
锁的其它分类(15 种锁速览)
| 维度 | 分类 | 一句话 |
|---|---|---|
| 是否加锁 | 悲观锁 / 乐观锁 | 先加锁 vs 更新时检查(CAS) |
| 是否自旋 | 自旋锁 / 适应性自旋锁 | 失败后循环重试 vs 自适应次数 |
| 公平性 | 公平锁 / 非公平锁 | 按申请顺序 vs 可插队(吞吐高但可能饥饿) |
| 可重入 | 可重入锁 / 非重入锁 | ReentrantLock 靠 state 计数支持重入 |
| 独占性 | 共享锁 / 排他锁 | 读锁可并发 vs 写锁独占 |
| 粒度 | 分段锁 | ConcurrentHashMap 按段加锁降低竞争 |
公平锁 vs 非公平锁
- 公平锁:按申请顺序进队列,队首才获锁 → 不饥饿,但吞吐低(除队首外全阻塞、唤醒开销大)
- 非公平锁:新线程直接尝试抢锁,抢不到才排队 → 吞吐高(有机会不阻塞),但队列线程可能饥饿
常见误解(避坑)
- ❌ "锁状态可以降级"。→ synchronized 锁只能升级不能降级(偏向锁撤销会回到无锁或轻量级,但整体路径是单向升级的)。
- ❌ "synchronized 底层直接就是重量级锁"。→ JDK6 后有偏向锁/轻量级锁两级缓冲,多数单线程/轻竞争场景根本到不了重量级。
- ❌ "轻量级锁就一定比重量级快"。→ 轻量级锁靠自旋,竞争激烈时大量线程空转占 CPU,反而不如直接阻塞(所以超限后升级)。
- ❌ "ReentrantLock 和 synchronized 性能天差地别"。→ JDK6 锁优化后两者性能接近;差异在功能(可中断、可超时、可条件变量、公平锁),不在绝对速度。
关联
- 原始资料:Java中的锁(本地锁)
- 总览:并发编程技术栈总览
- 相关卡:概念卡片:CAS乐观锁与原子类 · 概念卡片:AQS同步器框架
- 域地图:A00-百科/Java后端/Java后端