自适应自旋是JVM在轻量级锁阶段根据历史成功率、持有线程运行状态、上次自旋耗时及系统负载等动态调整自旋次数的优化机制,目标是在避免线程挂起开销与防止CPU空转间实现智能平衡。

自适应自旋不是预设一个固定次数去“硬等”,而是 JVM 在运行中持续观察同一把锁的抢夺表现,用历史数据实时调整本次该忙等几轮。它作用于轻量级锁阶段,核心目标是在避免线程挂起开销和防止 CPU 空转之间找动态平衡点。
它看哪些历史数据?
JVM 内部为每个锁对象(或锁热点)维护一组运行时指标:
- 近期自旋成功率:比如最近 5 次尝试中,有 4 次在自旋阶段就拿到了锁,成功率 80%,下次就会倾向多给几次机会
- 持有线程是否仍在运行:若持有锁的线程状态是 RUNNABLE(没被挂起、没在 sleep 或 IO 中),说明锁很可能很快释放,值得多等
- 上次成功所需的平均自旋次数:如果上回只转了 3 次就拿到锁,这次可能默认给 6–10 次;若连续两次都转满上限也没抢到,下次直接跳过自旋
- 系统负载与 CPU 核数:多核且空闲时更愿意让线程自旋;单核或负载高时会主动压低自旋意愿
它怎么调整自旋次数?
调整不是拍脑袋,而是带权重的反馈式更新:
- 每次自旋成功后,成功率按加权移动平均更新:新率 = (旧率 × 7 + 1000) ÷ 8 —— 新结果占更大比重
- 成功率 > 50%:自旋次数 = min(当前值 + 增量, 最大上限),例如从 10 → 12 → 15
- 成功率 ≤ 50%:自旋次数 = max(当前值 ÷ 2, 最小下限),例如从 10 → 5 → 2 → 可能归零,直接阻塞
- 这个过程完全由 HotSpot 内部逻辑控制,-XX:PreBlockSpin 参数在启用自适应后基本不起作用
哪些情况它会“放弃治疗”?
自适应机制本身包含快速降级策略,一旦识别出无效等待,立刻止损:
- 锁内含阻塞操作:比如 synchronized 块里调用了 Thread.sleep(100)、JDBC 查询、文件读写——JVM 检测到持有线程进入 TIMED_WAITING 或 BLOCKED 状态,后续对该锁的请求基本跳过自旋
- 连续失败信号明确:某锁连续多次自旋耗尽仍未获取,JVM 将其标记为“低成功率锁”,后续所有竞争者绕过自旋,直奔重量级锁流程(进入 _EntryList 阻塞)
- 单核 CPU 环境:没有其他线程能并发执行并释放锁,自旋失去意义,自动禁用
- 系统整体高负载:CPU 使用率长期超阈值,JVM 主动缩减自旋预算,优先保障调度公平性
怎么确认它正在工作?
不能只查参数,得看运行时行为:
- 用 jstack 抓堆栈,观察大量线程是否处于 RUNNABLE 状态、停在 Unsafe.park() 调用前的循环附近(如 AbstractOwnableSynchronizer 类中)
- 开启锁竞争日志:-Xlog:monitoring=debug,关注输出中某锁的 spin / block 比例变化——从全 spin 到 spin=0/block 暴增,说明自适应已生效降级
- 用 jol 查看对象头 Mark Word:若看到锁标志位在 00(轻量级锁)与 10(重量级锁)间随争用强度切换,且中间伴随自旋行为,就是自适应路径在起作用

















