自旋锁不能直接用while(true)因会吃满CPU且无内存可见性保障,需用volatile或AtomicReference等保证happens-before;应避免手写普通布尔变量循环,推荐AtomicReference/Integer表示状态,解锁用set()或lazySet()而非compareAndSet()。

自旋锁为什么不能直接用 while(true)
因为死循环会吃满 CPU,且没有内存可见性保障。Java 中的普通变量在多线程下可能被缓存到寄存器或 CPU 本地缓存,while(flag == false) 可能永远不退出,哪怕另一个线程已把 flag 设为 true。
必须配合 volatile 或原子类的内存语义——AtomicReference 的 get() 和 compareAndSet() 都有 happens-before 保证,能强制读取主内存最新值。
- 别手写
while(flag)+ 普通布尔变量,这是最常见翻车点 -
AtomicReference<boolean></boolean>不推荐:装箱开销大,且布尔值本身无法体现“锁状态”的原子切换意图 - 更合适的是用
AtomicReference<thread></thread>或AtomicInteger表示持有者/计数
compareAndSet() 是核心,但不是万能的
自旋锁靠反复尝试 compareAndSet(null, currentThread) 来抢锁。成功即获得锁;失败就继续循环。但要注意:
- 失败后不能立刻重试,否则在高竞争下造成大量 CAS 失败和总线争用,拖慢所有线程
- 建议加入简单退避:比如
Thread.onSpinWait()(JDK9+),或短睡眠Thread.yield(),甚至小次数的LockSupport.parkNanos(1) -
compareAndSet()返回false不代表锁被别人长期持有,可能是伪共享、缓存未及时同步,或只是瞬时竞争
示例关键逻辑:
立即学习“Java免费学习笔记(深入)”;
while (!state.compareAndSet(null, Thread.currentThread())) {
Thread.onSpinWait(); // JDK9+ 推荐,比空转友好
}
解锁必须用 set() 或 lazySet(),不能用 compareAndSet(old, null)
解锁是单线程行为(只有持有者能解),不需要原子性验证。用 compareAndSet(currentThread, null) 多此一举,还可能因线程对象被 GC 或复用导致误判。
- 正确做法:
state.set(null)或更轻量的state.lazySet(null)(避免写屏障,适合单写场景) - 如果用了
AtomicInteger做状态(0=空闲,1=占用),解锁就是state.set(0) - 千万别在解锁时检查当前线程是否匹配——这反而引入不必要的分支和内存读,且破坏了“解锁只由持有者执行”的前提
自旋锁不适合长时间持锁或低频竞争场景
它本质是用 CPU 时间换上下文切换开销。一旦临界区稍长(比如 > 几百纳秒),或者锁竞争不激烈,自旋就纯属浪费资源。
- 典型适用场景:锁住极短操作,如更新一个计数器、修改一个标志位、无锁队列的 head/tail 指针调整
- 不适用场景:IO 等待、数据库查询、任意可能阻塞或耗时的操作
- 生产环境慎用裸自旋锁;优先考虑
java.util.concurrent.locks.StampedLock的乐观读,或ReentrantLock的tryLock()配合退避策略
真正难的不是写出来,而是判断「此刻该不该自旋」——这得看热点路径的实测延迟分布,而不是凭感觉。









