我们继续用「全家共用洗衣机」的例子,类比 偏向锁、轻量级锁、重量级锁,这三种锁是 Java 为了优化 synchronized
性能,针对不同线程竞争场景设计的「锁升级策略」。
🌊 背景:锁的“进化史”
Java 的 synchronized
锁并不是一开始就很“重”,而是会根据竞争情况 自动升级:
偏向锁(单线程友好)→ 轻量级锁(少量线程竞争)→ 重量级锁(激烈竞争)
就像你用洗衣机时,根据使用场景选择不同的“门锁模式”。
1. 偏向锁:“专属钥匙”,单线程独享
✨ 适用场景:
当只有你一个人用洗衣机(单线程反复获取同一把锁),JVM 会给你一把“偏向锁”(专属钥匙),之后你再用洗衣机时 不用排队、不用检查锁,直接用钥匙开门,最快!
🍩 原理:
- 第一次用洗衣机时,锁对象会记录你的线程 ID(相当于在锁上贴“专属标签”),这就是“偏向”。
- 之后你每次来,锁检查标签发现是你,直接放行,连“CAS 竞争”都不需要(CAS 是一种轻量级的锁检查机制,后面会讲)。
🚫 缺点:
如果你妈突然也要用洗衣机(第二个线程来竞争),偏向锁就失效了,需要“撤销偏向”,升级为轻量级锁。
2. 轻量级锁:“快速敲门”,线程交替使用
✨ 适用场景:
你和你妈轮流用洗衣机(两个线程交替获取锁,没有同时竞争),这时候用“轻量级锁”,通过 CAS 快速检查锁是否可用,不用阻塞线程,效率高。
🍩 原理:
- 你和你妈都想拿锁时,不会直接排队(不像重量级锁那样阻塞),而是用 CAS 操作“快速敲门”:
- 锁内部有一个“指针”,指向当前持有者的线程。
- 你尝试用 CAS 把指针改成自己的线程 ID,如果成功,就拿到锁(不用阻塞)。
- 如果失败(比如你妈正在用),你会 自旋重试(原地快速等待一会儿,再试一次),而不是立刻阻塞。
🚫 缺点:
如果自旋多次都失败(比如你爸也来抢,三个线程竞争激烈),轻量级锁就会升级为重量级锁。
3. 重量级锁:“正式排队”,竞争激烈时启用
✨ 适用场景:
全家三个人都抢着用洗衣机(多线程激烈竞争),轻量级锁的自旋重试没用了,只能启用“重量级锁”—— 让抢不到锁的线程 进入阻塞状态(相当于去沙发上坐着等),等锁释放后再唤醒。
🍩 原理:
- 锁内部会关联一个 操作系统的互斥量(Mutex),竞争锁的线程会被操作系统挂起(放入等待队列),直到当前线程释放锁,操作系统再唤醒下一个线程。
- 这个过程涉及 用户态和内核态的切换(比如 Java 代码到操作系统的底层调用),开销很大(就像排队时需要登记、叫号,流程复杂)。
🚫 缺点:
虽然能保证互斥,但线程阻塞和唤醒的成本高,适合竞争激烈的场景。
🚪 锁升级过程对比(洗衣机场景)
锁类型 | 场景描述 | 锁机制(类比) | 核心特点 | 性能开销 |
---|---|---|---|---|
偏向锁 | 只有你用洗衣机,反复使用 | 锁上贴你的名字,直接开门 | 单线程专用,无竞争时最快 | 最低 |
轻量级锁 | 你和你妈轮流用,偶尔抢一下 | 快速敲门(CAS自旋),没人就进 | 线程交替使用,无阻塞,适合轻度竞争 | 中等 |
重量级锁 | 全家抢着用,频繁冲突 | 必须排队(操作系统阻塞队列) | 强制互斥,适合激烈竞争,但线程阻塞/唤醒开销大 | 最高 |
⚡ 为什么需要锁升级?
JVM 会“智能”选择锁策略:
- 无竞争/单线程:用偏向锁,省去所有锁检查步骤。
- 轻度竞争(交替使用):用轻量级锁,通过 CAS 自旋快速获取锁,避免阻塞。
- 重度竞争:用重量级锁,虽然开销大,但不得不保证互斥。
关键:锁升级是“从低开销到高开销”的渐进式策略,避免一开始就用重量级锁浪费资源,也避免轻度竞争时用重量级锁的高开销。
❓ 程序员需要知道的细节
- 锁升级是单向的:偏向锁 → 轻量级锁 → 重量级锁,只能升级不能降级(避免反复切换开销)。
- 如何触发偏向锁?
- JVM 默认开启偏向锁(参数
-XX:+UseBiasedLocking
),启动后延迟几秒生效(避免启动时多线程竞争)。
- JVM 默认开启偏向锁(参数
- 轻量级锁的自旋次数:
- 自旋次数有限(默认 10 次,可通过
-XX:PreBlockSpin
调整),超过就升级重量级锁。
- 自旋次数有限(默认 10 次,可通过
✨ 总结
- 偏向锁:单线程专属,贴标签直接用,最快。
- 轻量级锁:轻度竞争时,快速 CAS 自旋,避免阻塞。
- 重量级锁:激烈竞争时,强制排队,用操作系统底层机制保证互斥,最慢。
记住:Java 的 synchronized
会“因地制宜”,根据线程竞争情况自动选择最合适的锁,就像洗衣机根据使用人数切换“门锁模式”,目的是在保证线程安全的同时,尽可能减少性能损耗~