system.nanotime() 不适合控制自旋——因其调用开销大(20–50 纳秒)、tsc 跨核不同步、引发缓存抖动;应改用尝试次数+历史成功率的自适应策略,配合极简 cas 循环与 thread.onspinwait()。

不能用 System.nanoTime() 控制自旋过程——它本身开销不小,而且在不同 CPU 上 TSC(时间戳计数器)频率可能不一致,测出来的“纳秒耗时”不可靠,容易导致自旋策略失准。
为什么 nanoTime 不适合做自旋计时
自旋锁的核心诉求是:在极短时间内(比如几十到几百纳秒)判断是否该继续等待,还是该退让或挂起。而 System.nanoTime() 调用一次通常要 20–50 纳秒(取决于 JVM 和硬件),频繁调用反而掩盖了临界区的真实耗时,还引入额外分支和内存访问,破坏缓存局部性。
- 每次调用都触发 JNI 进入本地方法,有固定开销
- TSC 频率在多核/多 socket 系统中可能不同步,跨 CPU 读取的 nanoTime 值不可比
- 高并发下大量线程同时调用,会加剧 cache line bouncing(缓存行抖动)
真正有效的自适应策略:基于尝试次数 + 历史成功率
JDK 的 synchronized 在轻量级锁阶段就采用类似思路:不看绝对时间,而看“最近几次自旋是否成功”。成功多就多旋几次,失败多就快速放弃。
- 用一个 volatile 整型记录当前建议自旋次数(如初始为 10)
- 每次 lock() 尝试前,按当前次数循环 CAS;若中途成功,记录“本次成功”,并小幅增加下次次数(如 +1)
- 若全部失败,则大幅削减次数(如 ×0.7),并考虑调用
Thread.onSpinWait()或Thread.yield() - 可选:用
AtomicIntegerArray按 CPU 核心号隔离计数,避免伪共享
CAS 自旋主体应极简,只做核心原子操作
自旋循环体内必须干净:仅含一次 CAS 尝试 + 极轻量控制逻辑。任何日志、计时、条件判断都会拖慢关键路径。
- 推荐结构:
while (!state.compareAndSet(0, 1)) { Thread.onSpinWait(); } -
Thread.onSpinWait()映射为 x86 的 PAUSE 指令,降低功耗、缓解流水线冲突,比空循环友好得多 - 避免在循环里调用
System.nanoTime()、System.currentTimeMillis()、Thread.currentThread()等非内联方法
需要超时或退让时,用 parkNanos 替代时间计算
如果确实要限制总等待时长(例如防止极端情况死等),应在自旋达到预设次数后,直接调用 LockSupport.parkNanos(timeoutNs),而不是自己累加 nanoTime。
-
parkNanos是 JVM 优化过的原生挂起,底层由 OS 调度器保障精度,且不会干扰自旋主路径 - timeoutNs 可设为固定值(如 1000 纳秒)或根据历史平均持有时间动态估算,但不要实时算
- 注意:parkNanos 仍属阻塞操作,只应在自旋失效后启用,不是自旋的一部分











