自适应自旋根据同一把锁的历史表现动态调整自旋次数,依据锁持有者是否存活、历史成功率及cpu与竞争线程比值;固定自旋易在高并发短临界区或混合负载下失效,且在cpu过载、单核、含阻塞操作或锁复用场景中反而拖垮性能。

自适应自旋锁到底在“自适应”什么?
它不是调大或调小一个固定次数,而是让 JVM 根据**同一把锁的历史表现**动态决定这次该 spin 多久。比如上次 tryLock() 在第 3 次就成功了,这次可能给到 15 次机会;如果上次 spin 到底都没抢到,还进了 park(),那这次可能只给 2 次——甚至直接跳过自旋,走重量级锁流程。
关键判断依据有三个:
• 锁持有者线程是否仍在运行(is_alive())
• 上次在该锁上的自旋成功率
• 当前 CPU 核心数与竞争线程数比值
也就是说,它像一个带记忆的交通灯:路口刚通畅过,就多等几秒绿灯;连续堵车,立刻切红灯进队列。
为什么不能关掉自适应,改用固定自旋次数?
因为固定值(比如用 -XX:PreBlockSpin=10)在真实场景里大概率失灵:
立即学习“Java免费学习笔记(深入)”;
- 高并发短临界区(如计数器更新):10 次不够,刚 spin 完锁就释放了,白白错过
- 低频长任务(如 IO 后置处理):10 次早超时,但线程其实本可等更久——不过这种场景本就不该用自旋
- 混合负载服务(如 Spring Boot 应用):不同业务锁行为差异极大,一刀切参数必然拖累某类请求
-XX:+UseSpinning 在 JDK 6+ 默认开启,而自适应逻辑是它默认启用的核心部分;强行关闭(-XX:-UseAdaptiveSpinning)等于退回到 JDK 1.4 的“盲等”时代,不推荐。
哪些场景下自适应自旋反而会拖垮性能?
它聪明,但不是万能的。以下情况 JVM 的自适应策略容易失效,甚至放大问题:
-
CPU 使用率 > 90%时还在自旋:所有线程原地空转,响应延迟飙升,错误日志里常伴随java.lang.Thread.State: RUNNABLE却无实际进展 - 单核 CPU 或容器限制为 1vCPU:自旋毫无意义,因为持有锁的线程根本没机会被调度出去
- 临界区含阻塞操作(如
Thread.sleep()、Object.wait()、DB 查询):锁持有时间不可控,自旋纯属浪费 - 锁对象被高频复用(如静态
Object LOCK = new Object()):多个无关业务共用一把锁,历史成功率失真,自适应参考价值归零
这类问题不会报错,但你会看到 GC 日志正常、线程 dump 里一堆 RUNNABLE 状态线程卡在 synchronized 入口——这是最典型的自适应失效信号。
怎么验证当前应用是否真的受益于自适应自旋?
别猜,看数据。最直接的方式是开启 JVM 锁竞争统计:
启动时加参数:-XX:+PrintGCDetails -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1,然后观察 safepoint 日志中 spin 相关字段(如 SpinAcquire、SpinBeforeEnter 的计数和平均耗时)。
更轻量的方法是用 jstack 抓几次快照对比:
jstack <pid> | grep -A 5 "java.lang.Thread.State: RUNNABLE" | grep "synchronized"
如果大量线程反复出现在同一行 synchronized 调用上,且堆栈深度极浅(比如就两三级),基本就是自旋热点。这时候再结合 top -H -p <pid></pid> 看对应线程的 CPU 占用,就能确认是不是自旋在吃资源。
真正难的是区分“该优化锁设计”还是“该调 JVM 参数”——这取决于锁的语义是否合理。自适应再智能,也救不了设计错的锁。










