java中需用atomicinteger手写自旋锁,以compareandset(0,1)尝试获取锁,失败时调用thread.onspinwait()提示cpu优化忙等,避免空转开销;jdk8及以前可用thread.yield()替代但效果较差。

Java里用AtomicInteger手写自旋锁最常用
自旋锁在Java中没有现成的SpinLock类,得靠java.util.concurrent.atomic包里的原子操作自己搭。核心思路是用一个AtomicInteger(或AtomicBoolean)表示锁状态:0代表空闲,1代表已被占用。
典型实现会循环调用compareAndSet(0, 1)尝试抢锁,成功则进入临界区;失败就“原地打转”继续重试——这就是自旋。注意不能用while (state.get() == 1)这种轮询读,那不是原子的,会导致ABA或丢失更新。
示例片段:
public class SimpleSpinLock {
private final AtomicInteger state = new AtomicInteger(0); // 0: free, 1: locked
public void lock() {
while (!state.compareAndSet(0, 1)) {
// 自旋等待,可加Thread.onSpinWait()(JDK9+)
}
}
public void unlock() {
state.set(0);
}
}
为什么Thread.onSpinWait()不能少
裸循环while(!cas)会让CPU持续执行空指令,不仅浪费算力,还可能干扰其他线程调度、加剧缓存一致性开销。JDK9引入的Thread.onSpinWait()是个提示指令,告诉JVM和底层硬件:“我正在忙等,别急着调度我,也请优化缓存行刷新”。它不保证行为,但现代HotSpot和x86/ARM处理器基本都会响应。
立即学习“Java免费学习笔记(深入)”;
实操建议:
- 必须放在
compareAndSet失败之后、下一次重试之前 - 不要在每次循环都调用——可配合简单退避(如每10次自旋调用一次)避免过度提示
- JDK8及更早版本没有该方法,需用
Thread.yield()替代,但效果差很多,且可能让出CPU导致延迟突增
自旋锁不适合长临界区,否则反而拖慢整体吞吐
自旋锁只适合临界区极短(通常建议
常见误用现象:
- 在
lock()后调用System.out.println()或Logger.info()→ 日志框架内部有锁或IO - 临界区内做网络请求、数据库查询、文件读写 → 完全违背自旋设计初衷
- 高争用下多个线程同时自旋 → CPU使用率飙升,实际吞吐暴跌,甚至引发JVM GC压力
如果你发现加了自旋锁后RT没降反升、CPU跑满,大概率是临界区太重,该换ReentrantLock或synchronized了。
真正生产环境几乎不用手写自旋锁
JDK并发包里所有公开API(包括StampedLock的乐观读、LongAdder的分段累加)内部确实用了自旋逻辑,但它们都经过大量压测、针对不同CPU缓存层级做了适配,还混合了退避、yield、park等策略。你自己写的简单版,在多核NUMA机器或Contended场景下很容易因缓存行伪共享、内存序乱序等问题表现不稳定。
除非你在写高性能中间件底层(比如RPC框架的无锁队列、内存池分配器),否则直接用ReentrantLock + tryLock(timeout, unit)模拟有限自旋更稳妥。JDK本身也只在极少数路径(如AbstractQueuedSynchronizer的acquire时短暂自旋)才谨慎启用自旋。
最容易被忽略的一点:自旋锁无法被JVM线程dump识别为“BLOCKED”,它显示为RUNNABLE,排查时容易误判为CPU密集型业务代码,实际却是锁竞争卡死——这点在线上问题定位时特别坑。










