自定义自旋重试是为单线程高频写、极短临界区等特定场景设计的轻量锁,通过volatile int state和volatile Thread waiter实现,仅用CAS+有限自旋+park兜底,规避synchronized的偏向锁撤销、Lock Record创建及内核态切换开销。

自定义自旋重试不是为了“打败”synchronized,而是针对特定场景——比如单线程高频写、极短临界区、无重入/中断/公平性需求——用更少状态、更少分支、更贴近硬件的方式控制并发。它的轻量,来自对锁语义的主动裁剪,而非单纯替换关键字。
核心思路:用 volatile + 有限自旋 + park 兜底
不依赖对象头 Mark Word,也不走 AQS 队列,只靠一个 volatile int state(0=空闲,1=占用)和一个 volatile Thread waiter 记录等待者:
- 获取锁时,先循环 CAS 尝试设 state 为 1,自旋次数建议 100–200 次(JDK 17+ x86 实测较稳)
- 自旋失败后调用
LockSupport.park()进入阻塞,避免 CPU 空转 - 释放锁时设 state = 0,并对 waiter 调用
unpark(),然后清空 waiter 字段 - 全程不分配 Node 节点、不维护队列、不处理中断或超时,内存开销压到最低
为什么比 synchronized 在某些场景更轻?
不是它本身更快,而是它跳过了 synchronized 的自动升级路径中“不必要”的环节:
- 偏向锁撤销会触发安全点(Safepoint),带来 STW 停顿;而自定义锁从不记录线程 ID,无撤销成本
- 轻量级锁需在栈帧中创建 Lock Record、拷贝 Mark Word、做多次 CAS 替换;自定义锁只做一次 volatile 写 + 若干次 CAS 读写
- 重量级锁会进入内核态挂起线程;自定义锁把 park 当作兜底策略,自旋阶段全在用户态完成
如何根据变量计算耗时定制自旋次数?
自旋不是越久越好,关键看临界区平均执行时间与线程切换代价的平衡。可参考这个估算逻辑:
- 若临界区平均耗时
- 若变量更新涉及简单字段赋值(如
counter++),且无 I/O 或锁内调用,适合高自旋(200–500) - 若变量是缓存命中率高的本地状态(如 L1 cache 中的布尔标记),可动态设为
Runtime.getRuntime().availableProcessors() * 64 - 避免硬编码:用
@Contended分离 state 和 waiter 字段,防止伪共享影响 CAS 效率
适用边界必须清楚
这种锁不是通用替代品,只在以下条件同时满足时才有意义:
- 临界区代码极简(通常 ≤ 10 行纯计算,无 synchronized 方法调用、无 wait/notify)
- 竞争强度低(例如读多写少,或写线程固定为 1 个)
- 不要求可重入、不可中断、不需 Condition、不关心公平性
- 你愿意承担调试难度上升的代价(没有 JFR 可视化、无标准 lock profiling 支持)

















