轻量级锁自旋与重量级锁挂起由JVM根据临界区实际执行时间动态决定:若持有线程能极短时间内释放锁,则自旋抢锁;否则自旋失败后升级为重量级锁并挂起线程。

轻量级锁的自旋和重量级锁的挂起,不是由程序员显式选择的,而是 JVM 根据锁竞争实时动态决定的——核心依据是“持有锁的线程是否能在极短时间内释放锁”。
自旋发生在用户态,只为抢回毫秒级临界区
当线程尝试获取一个已被占用的轻量级锁时,JVM 不会立刻让该线程休眠,而是让它在用户态执行有限次数的 CAS 尝试(默认最多 10 次,可通过 -XX:PreBlockSpin 调整)。这个过程就是自旋。
- 自旋只适用于临界区极短的操作,比如原子计数器递增、布尔状态翻转、简单字段赋值
- 若持有锁的线程恰好在几次 CPU 周期内完成并释放锁,自旋线程就能立即拿到锁,避免昂贵的上下文切换
- 自旋不涉及内核调度,不释放 CPU 时间片,但会持续占用一个逻辑核
- 单核 CPU 上自旋基本无效,多核高竞争下还可能加剧缓存一致性开销
挂起进入内核态,是自旋失败后的保底策略
一旦自旋次数耗尽仍未成功获取锁,JVM 判定“当前锁持有时间已超出预期”,于是触发锁膨胀:轻量级锁升级为重量级锁,争用线程转入操作系统内核等待队列。
- 线程状态从 RUNNABLE 变为 BLOCKED,主动让出 CPU,交由 OS 调度器管理
- 挂起后线程不再消耗 CPU,但唤醒需经历用户态→内核态→用户态两次切换,开销通常在微秒级
- 挂起等待适合中长临界区,如数据库查询、远程调用、文件读写等无法预估执行时长的操作
- 此时锁对象头 Mark Word 被替换为指向 Monitor 的指针,真正依赖操作系统互斥量实现
决策关键不在代码写法,而在临界区实际执行时间
同一个 synchronized 块,在不同运行条件下可能走完全不同的锁路径。JVM 不看你的代码写了什么,只看它实际跑得多快。
立即学习“Java免费学习笔记(深入)”;
- 如果同步块里只做 i++ 或 flag = true,大概率全程轻量级锁 + 自旋
- 如果同步块里调用了 Thread.sleep(10) 或 jdbcTemplate.query(),哪怕只调一次,自旋几乎必然失败,很快升级为重量级锁
- JDK 9+ 默认启用自适应自旋:JVM 会根据前几次该锁的平均持有时间,动态调整本次自旋次数,比固定阈值更智能
- 可通过 -XX:+PrintGCApplicationStoppedTime 或 JFR 采集锁膨胀事件,验证实际升级时机
不建议强行干预,但可针对性规避风险
手动关闭自旋(-XX:-UseSpinning)或禁用轻量级锁(-XX:-UseLightweightLocking)通常得不偿失。更有效的方式是优化临界区本身:
- 把 I/O、网络、复杂计算等耗时操作移出 synchronized 块
- 用更细粒度的锁(如 ConcurrentHashMap 替代 Hashtable)降低竞争概率
- 对纯读场景,优先考虑无锁结构(如 CopyOnWriteArrayList)或读写锁
- 避免在 synchronized 内调用未知行为的第三方方法,它们可能隐含阻塞


















