自旋锁不阻塞线程,靠CAS忙等抢锁,CPU时间片持续运行但空耗;高争用时易占满单核,应配合Thread.onSpinWait()、合理退避策略及临界区特性优化。

Java里用AtomicInteger写自旋锁,为什么死循环不卡主线程?
因为自旋锁本身不阻塞线程,它靠反复调用compareAndSet抢锁,CPU时间片还在跑,只是不做别的事。但这也意味着:如果锁竞争激烈或持有时间长,while(!cas)会白占CPU,吞性能。
- 别在高争用场景(比如10+线程抢同一把锁)直接裸写自旋,容易把一个核跑满
-
compareAndSet失败后建议加Thread.onSpinWait()(JDK9+),提示CPU当前是忙等,可能触发底层优化(如降频、让出流水线) - 注意JVM参数:
-XX:+UsePauselessGC或ZGC下,自旋对GC线程干扰更小;但CMS或Parallel GC时,自旋期间若触发STW,可能延长停顿
自旋次数设多少才合理?硬编码100还是用指数退避?
固定次数太僵硬——短临界区可能10次就拿到锁,长临界区100次根本不够;指数退避又容易拖太久。实际看的是「平均持锁时间」和「线程数」。
- 如果临界区操作稳定在微秒级(如计数器++),用固定小值(
16~64)足够,再失败就让出(Thread.yield())或升级为阻塞锁 - 涉及I/O或不确定耗时的代码,别自旋,直接上
synchronized或ReentrantLock - 避免用
System.nanoTime()算耗时来控制自旋——获取纳秒时间本身就有开销,且不同CPU TSC频率可能不一致
Unsafe.compareAndSwapInt比AtomicInteger.compareAndSet快吗?
几乎没差别。现代JDK中AtomicInteger.compareAndSet就是内联调用Unsafe.compareAndSwapInt,JIT还会做进一步优化。手动用Unsafe反而增加维护成本和安全隐患。
-
Unsafe操作绕过Java内存模型校验,容易写出违反happens-before的代码(比如漏掉volatile语义) - JDK17开始
Unsafe很多方法被标记为@Deprecated(forRemoval = true),未来会被移除 - 真要极致性能,优先考虑减少锁粒度或用无锁数据结构(如
ConcurrentLinkedQueue),而不是抠Unsafe那点指令
自旋锁和ReentrantLock的tryLock()有什么本质区别?
tryLock()是「尝试一次即返回」,而自旋锁是你自己写的「尝试N次再放弃」逻辑。关键差异不在CAS本身,而在是否交由AQS调度。
立即学习“Java免费学习笔记(深入)”;
-
tryLock()失败后,线程状态仍是RUNNABLE;但若后续调用lock(),就会进AQS队列并可能挂起(WAITING/TIMED_WAITING) - 纯自旋锁全程不进AQS,不参与线程调度排队,所以没有唤醒开销,但也得不到操作系统级别的公平性保障
- 混合方案常见:先自旋几十次,失败后走
ReentrantLock.lock(),兼顾低争用响应速度和高争用资源效率



















