synchronized自旋仅在轻量级锁竞争时发生,且受JVM自适应策略调控,并非必然导致CPU飙升;真正问题在于临界区过长、锁粒度粗或高竞争设计缺陷。

synchronized 锁的自旋等待确实会占用 CPU,但影响程度取决于具体场景和 JVM 版本的优化策略,并非“一开就飙高”或“完全无感”。关键不在“有没有自旋”,而在于“自旋多久、谁在自旋、锁被占多久”。
自旋只发生在轻量级锁竞争阶段
并不是所有 synchronized 都会自旋。只有当锁处于轻量级锁状态(即没有膨胀为重量级锁),且多个线程同时尝试获取同一把锁时,失败的线程才会进入自旋等待。一旦锁膨胀为重量级锁(比如竞争持续、自旋失败次数超限),后续线程就直接 park 进入阻塞态,不再消耗 CPU。
- 自旋仅针对“短时持有 + 多核环境”的乐观预期,单核机器上 JVM 通常跳过自旋
- 自旋发生在用户态,不涉及内核态切换,所以开销比挂起/唤醒小,但确实在跑空循环
- JDK 6+ 默认开启自适应自旋,JVM 会根据该锁的历史表现动态调整本次自旋轮数(比如上次自旋成功了,这次多试几轮;上次全失败,这次可能直接跳过)
CPU 占用不是线性增长,而是“局部尖峰+快速收敛”
自旋本身不导致全局 CPU 持续 100%,因为:
- 默认自旋次数有限(HotSpot 早期默认 10 次,现代版本更智能,通常几十轮内就退避)
- 每次自旋后通常插入 Thread.onSpinWait()(Java 9+),提示 CPU 可能有缓存同步意图,有助于降低功耗和缓存行争用
- 若自旋失败,线程立刻 park,进入操作系统调度队列,彻底释放 CPU
- 真正造成 CPU 飙高的,往往是多个线程反复在同一个锁上自旋+失败+再自旋——这说明锁粒度太粗或临界区太重,问题根源不在自旋机制,而在业务设计
什么情况下自旋会明显拖垮 CPU?
不是自旋本身危险,而是它暴露了底层问题:
立即学习“Java免费学习笔记(深入)”;
- 临界区执行时间过长:比如 synchronized 块里做了 I/O、远程调用、复杂计算——此时自旋毫无意义,只会白烧 CPU
- 高竞争低吞吐场景:如多个线程频繁争抢同一个计数器或共享 map 的单个桶,导致大量线程反复自旋又失败
- 锁未合理分段或隔离:用一个 synchronized(this) 保护整个对象,而实际只需保护某个字段——扩大了竞争面
- 误用“无锁”假象:以为 ConcurrentHashMap.putIfAbsent() 完全无锁,实则其内部桶操作在高并发下仍可能触发自旋逻辑,叠加使用会放大效应
怎么判断和缓解?
不用猜,靠观测和重构:
- 用 jstack 查看线程堆栈,如果大量线程卡在
Unsafe.park前的循环中(如AbstractQueuedSynchronizer$Sync.tryAcquire内部忙等),说明自旋正在发生 - 用 perf stat -e cycles,instructions,LLC-load-misses 观察是否出现高频缓存失效(cache line bouncing),这是多核自旋争抢的典型信号
- 优先改代码:把大锁拆小(锁方法 → 锁代码块)、用 LongAdder 替代 synchronized 计数、用 ConcurrentHashMap.computeIfAbsent 替代手动同步
- 极端情况可关闭自旋(
-XX:-UseSpinning),但这是掩耳盗铃——治标不治本,反而可能因频繁 park/unpark 降低吞吐


















