atomicreference 的 cas 操作本质是调用 unsafe.compareandset() 触发硬件级 cmpxchg 指令实现原子更新,不阻塞线程,失败返回 false,需手动循环重试;其本身不提供自旋封装,仅保证单次操作的原子性。

AtomicReference 的 CAS 操作本质是什么
它不是“自己实现 CAS”,而是调用 Unsafe.compareAndSet() 底层指令完成原子更新。Java 层面的 AtomicReference 就是把一个对象引用包装成可被 CAS 安全修改的容器,每次 compareAndSet(expected, updated) 都会触发一次硬件级 CMPXCHG(带 LOCK 前缀),失败时返回 false,不阻塞、不挂起线程。
关键点在于:你必须自己写循环重试逻辑,AtomicReference 本身不提供“自旋等待”封装 —— 它只负责单次 CAS 是否成功。
为什么不能直接用 compareAndSet 写锁,而要加自旋
因为 compareAndSet() 是一次性尝试,失败就立刻返回。如果目标是模拟“获取锁”的语义(比如想让线程等到能更新成功为止),就必须手动包裹一层循环,否则调用方看到 false 就得自己决定是放弃还是重试。
- 不加自旋 → 相当于“乐观尝试一次”,适合低冲突场景或非关键路径
- 加自旋 → 线程持续占用 CPU 资源轮询,直到 CAS 成功,适合临界区极短、竞争不激烈的情况
- 自旋过久 → 可能比直接用
synchronized更耗资源,尤其在高争用或临界区较长时
一个最小可行的无锁自旋锁实现
核心是用 AtomicReference 存储当前持有锁的线程(Thread 对象),通过 CAS 控制“只有未上锁时才能设为当前线程”:
public class SpinLock {
private final AtomicReference<thread> owner = new AtomicReference();
public void lock() {
Thread current = Thread.currentThread();
while (!owner.compareAndSet(null, current)) {
// 自旋等待:什么也不做,或加一点 yield 减少 CPU 占用
Thread.onSpinWait(); // JDK9+ 推荐,比空循环更友好
}
}
public void unlock() {
Thread current = Thread.currentThread();
if (!owner.compareAndSet(current, null)) {
throw new IllegalMonitorStateException("not owner");
}
}
}</thread>
注意几个易错点:
-
unlock()必须校验当前线程是否为持有者,否则可能误释放他人锁 - 没有可重入逻辑 —— 同一线程重复
lock()会死锁,如需可重入,得额外记录计数,且仍要用 CAS 更新整个状态(例如用AtomicInteger编码 threadId + count) -
Thread.onSpinWait()不是必须,但比Thread.yield()或空循环更合适:它向 CPU 发出提示,表示当前在忙等,允许硬件优化(如降低频率、跳过流水线)
实际部署前必须验证的三个边界
这类组件极易在真实负载下失效,不是写出来就能用:
- ABA 问题:如果锁被 A 获取 → B 释放 → A 再获取,
AtomicReference无法感知中间变化。虽然对锁模型影响小(只关心是否为空),但若扩展成栈/队列等结构,就必须用AtomicStampedReference - 公平性缺失:完全无序竞争,饥饿风险存在;
synchronized在 JVM 层有队列化机制,而纯 CAS 自旋锁没有 - JIT 优化干扰:极端情况下,JIT 可能将自旋循环判定为“不可达”而优化掉(虽罕见),建议在循环体中加入
Blackhole.consumeCPU(1)(JMH 工具类)或 volatile 读作为防优化锚点
真正难的从来不是写出一个能跑通的自旋锁,而是判断它在哪种并发强度、哪种临界区长度、哪种 CPU 核心数下不会反向拖慢系统 —— 这些只能靠压测,没法靠代码逻辑推导出来。











