cas本身不直接实现自旋等待,而是为自旋提供原子基础;轻量级锁在尝试获取锁失败时基于cas循环检测锁状态,形成忙等待,避免线程挂起和上下文切换,适用于短临界区场景。

CAS(Compare-And-Swap)本身不直接“实现”自旋等待,而是为自旋提供原子基础;轻量级锁(如 synchronized 的偏向/轻量级阶段或 java.util.concurrent 中的 AQS 同步器)在尝试获取锁失败时,会基于 CAS 循环检测锁状态,从而形成自旋等待——它用忙等待代替挂起线程,避免进入内核态,减少上下文切换开销。
自旋等待如何绕过线程挂起
当一个线程发现锁被占用(比如对象头中的 Mark Word 表示锁已被持有),它不会立刻调用 park() 进入阻塞态,而是反复执行 CAS 操作尝试抢锁。只要持有锁的线程很快释放(例如临界区极短),自旋线程大概率在下一次 CAS 中成功,全程停留在用户态,不触发 OS 调度。
- 每次自旋都是一次无锁的、非阻塞的 CAS 尝试,CPU 时间片仍在当前线程手中
- 没有线程状态变更(RUNNABLE → WAITING/BLOCKED),也就无需保存/恢复寄存器、栈等上下文
- 避免了陷入内核、调度器介入、队列管理等开销,对短临界区非常高效
JVM 对自旋的优化策略
纯无限自旋会浪费 CPU,所以 HotSpot JVM 实际采用自适应自旋:根据前一次在同一锁上自旋是否成功、以及锁持有时间的历史数据,动态决定本次最多自旋多少次。
- 默认开启(
-XX:+UseSpinning,JDK 6+ 默认启用),最大次数由-XX:PreBlockSpin控制(旧版本)或由 JVM 自适应调整 - 若上次在该锁上自旋成功,本次更可能多旋几次;若多次失败,则快速退避,转为传统阻塞
- 自旋只发生在多核场景下才有意义——单核 CPU 上自旋不仅无效,还会阻碍持有锁线程运行
和 AQS 中自旋获取的联系
像 ReentrantLock 非公平模式下,acquire 流程中会先尝试一次 CAS 修改 state;失败后,并非立即 park,而是进入 acquireQueued 前的自旋检查(shouldParkAfterFailedAcquire 之前可能有数次重试),本质仍是利用 CAS + 循环判断实现轻量等待。
- 自旋逻辑封装在
AbstractQueuedSynchronizer的tryAcquire和队列节点入队前的检查中 - 用户可重写
tryAcquire方法,自行决定是否在 CAS 失败后继续自旋(例如带超时的自旋尝试) - 这种模式把“是否自旋”的决策权部分交给同步器实现,比单纯操作系统级阻塞更灵活
注意自旋不是万能的
自旋适合锁持有时间远小于线程挂起+唤醒开销的场景。一旦锁竞争激烈或临界区较长,持续自旋反而推高 CPU 使用率,降低整体吞吐。
- JVM 会限制自旋深度,防止过度消耗资源
- 高并发下多个线程同时自旋,可能引发总线争用、缓存行失效(false sharing),抵消收益
- 现代 JVM 更倾向结合逸出分析、锁粗化、锁消除等优化,从源头减少锁需求,而非仅依赖自旋
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











