轻量级锁升级为重量级锁的核心代价是用户态自旋转内核态阻塞,引发上下文切换、线程调度、内存同步三类开销叠加,具体包括1000–3000周期的上下文切换、TASK_UNINTERRUPTIBLE状态转换延迟、强内存屏障导致缓存失效,以及ObjectMonitor等原生内存分配成本。

轻量级锁转重量级锁的性能代价,不能简单用“多少毫秒”来量化,而要看它触发的系统行为变化——核心是从用户态自旋切换到内核态阻塞。这个转变带来的开销不是固定值,而是由上下文切换、线程调度、内存同步三类成本叠加而成,且高度依赖运行时环境。
轻量级锁升级为重量级锁时发生了什么
当多个线程频繁竞争同一把轻量级锁,JVM 判定自旋已无意义(比如连续失败超过阈值、或检测到锁持有者正在执行长任务),就会执行锁膨胀(Lock Inflation):
- 对象头的 Mark Word 从轻量级锁记录(指向栈中 Lock Record)更新为指向一个 ObjectMonitor 结构;
- 所有未抢到锁的线程被挂起,进入操作系统线程等待队列(Contention List → Entry List);
- 后续唤醒依赖 pthread_mutex 或类似内核互斥原语。
这个过程本身不耗时,但后续线程阻塞与唤醒才是代价主体。
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
主要性能代价来源
一次上下文切换约消耗 1000–3000 个 CPU 周期
用户态 → 内核态切换需保存寄存器、切换页表、更新调度状态。现代 CPU 上通常对应 0.5–2 微秒,但高负载下可能飙升。线程挂起/唤醒带来调度延迟
操作系统需将线程从运行态置为阻塞态(TASK_UNINTERRUPTIBLE),再在锁释放时将其重新入就绪队列。若系统已有大量就绪线程,唤醒后未必立即执行,排队等待时间不可控。缓存失效与内存屏障开销放大
重量级锁的 monitor enter/exit 插入强内存屏障(如 lock addl $0, (rsp)),强制刷新 Store Buffer 和使其他 CPU 的缓存行失效,影响多核间数据可见性同步效率。额外内存分配与管理成本
ObjectMonitor 是堆外结构(C++ new 出来),需 JVM 维护其生命周期;每个等待线程还关联 ParkEvent 等 OS 资源,增加内存与句柄压力。
实际估算参考(典型场景)
| 场景 | 单次锁升级引发的典型开销 | 说明 |
|---|---|---|
| 单线程快速重入(无竞争) | ≈ 0 ns | 不触发升级,始终停留在轻量级或偏向锁 |
| 双线程交替获取(轻度竞争) | 自旋失败 10 次 × ~50 ns = ~0.5 μs | 默认 -XX:PreBlockSpin=10,每次 CAS + 空循环极轻量 |
| 两线程激烈争抢(升级发生) | ≥ 1.5–5 μs(首次升级)+ 后续每次阻塞唤醒 ≥ 3–10 μs | 包含上下文切换、monitor 初始化、OS 调度延迟 |
| 高并发(10+线程争同一锁) | 每新增阻塞线程带来额外 ~2–8 μs 增量开销 | 竞争队列维护、唤醒公平性判断、虚假唤醒处理等 |
注:以上数值基于主流 x86_64 服务器(Linux 5.x + JDK 17/21),关闭了
UseBiasedLocking且未做特殊 GC 调优。实测中,若锁持有时间 > 100 μs,升级几乎必然发生;若平均持有时间 < 10 μs 且竞争线程 ≤ 2,轻量级锁可长期维持。立即学习“Java免费学习笔记(深入)”;
如何降低升级概率与代价
- 避免在 synchronized 块中做 I/O、sleep、远程调用等长耗时操作
- 缩小临界区范围,只包裹真正需要同步的代码
- 使用
ReentrantLock配合tryLock(timeout)主动控制等待行为,避免无条件阻塞 - 通过
-XX:+PrintCompilation -XX:+TraceClassLoading观察锁膨胀日志(如BiasedLockRevocation、InflatedMonitors) - 启用
-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics查看 monitor 相关 native memory 分配增长
不复杂但容易忽略


















