synchronized方法执行时线程未卡住是因为锁采用无锁→偏向锁→轻量级锁→重量级锁的渐进升级机制,低竞争下偏向锁和轻量级锁几乎无开销,仅重量级锁才导致线程挂起阻塞。

为什么 synchronized 方法执行时,线程没卡住却还是拿不到锁?
因为 synchronized 的锁不是一上来就用重量级互斥锁,而是从无锁 → 偏向锁 → 轻量级锁 → 重量级锁逐步升级的。你看到“没卡住”,很可能正处在偏向锁或轻量级锁阶段,竞争不激烈时几乎没开销。
- 偏向锁:只在第一次进入时 CAS 设置线程 ID,后续同一线程重入直接通过,零同步开销
- 轻量级锁:多个线程交替进入,用 CAS 替换对象头的 Mark Word,失败则自旋重试(默认 10 次)
- 重量级锁:自旋失败后膨胀为 OS 互斥量,线程挂起,真正阻塞——这时才出现你熟悉的“卡住”现象
- JVM 默认开启偏向锁(
-XX:+UseBiasedLocking),但启动 4 秒后才会激活;高竞争场景下,偏向锁反而因撤销开销更大,建议关闭
如何验证当前对象用了哪种锁状态?
靠打印对象头(Mark Word)最直接,但得用 jdk.internal.vm.annotation.IntrinsicCandidate 类或 JOL(Java Object Layout)工具。生产环境不推荐实时查,调试时可:
- 加 JVM 参数:
-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1,配合锁膨胀日志观察 - 用
jstack -l <pid></pid>查线程栈,带locked表示已进入重量级锁 - 注意:JOL 输出的锁状态是快照,且偏向锁撤销后对象头会变,不能仅靠一次输出判断全程行为
synchronized 锁升级会触发 GC 吗?
不会直接触发 GC,但锁膨胀过程本身有内存和 CPU 成本:
- 偏向锁撤销:需遍历所有线程的栈,检查是否持有该锁,可能引发 safepoint 停顿(尤其在大堆、多线程场景)
- 轻量级锁膨胀为重量级锁:会在对象头写入指向 Monitor 对象的指针,Monitor 本身分配在堆外(C++ new 出来),不走 Java 堆,但会增加 native 内存压力
- 频繁锁升级/降级(如大量短生命周期对象被 synchronized 包裹)会导致 Monitor 对象反复创建销毁,间接加重 JVM 管理负担
什么时候该考虑不用 synchronized?
不是所有临界区都适合它——尤其当锁粒度粗、等待时间长、或需要响应中断、超时、条件等待时:
立即学习“Java免费学习笔记(深入)”;
- 简单计数器:用
AtomicInteger比synchronized更轻量 - 需要 tryLock 或超时:必须换
ReentrantLock,synchronized不支持 - 读多写少:
ReadWriteLock或StampedLock更合适 - 锁内调用外部服务(如 DB、HTTP):极易导致锁持有时间不可控,应拆出同步块,或改用消息/异步+状态机
锁升级机制再聪明,也掩盖不了设计上“把不该串行的逻辑硬串行”的问题。真正难的从来不是理解锁怎么升,而是想清楚——这里到底要不要锁。






