atomicreference通过compareandset循环将null置为当前线程实现acquire,释放时校验并设回null;需用while重试、thread.onspinwait优化、volatile语义保障及避免可重入等缺陷。

AtomicReference 怎么模拟自旋锁的 acquire 和 release
核心是用 compareAndSet 循环尝试更新状态:把“未加锁”(比如 null 或 0)换成当前线程标识,成功即获得锁;释放时直接设回空值。它不挂起线程,失败就重试,所以叫“自旋”。
- 典型状态变量用
AtomicReference<thread></thread>,初始值为null;acquire()里循环调用compareAndSet(null, Thread.currentThread()) - 必须用
while (!tryAcquire()) { Thread.onSpinWait(); }而不是if,否则一次失败就退出,根本没锁住 -
release()必须校验当前线程是否持锁(get() == Thread.currentThread()),避免误释放 —— 这点synchronized自动保证,手写必须自己兜底 -
Thread.onSpinWait()不是必需但强烈建议加上,它提示 CPU 当前在自旋,能降低功耗、提升后续 CAS 成功率
为什么在低竞争 + 短临界区场景下比 synchronized 快
因为省掉了操作系统线程调度开销:synchronized 在竞争激烈时会把线程挂起进 WaitSet,唤醒涉及用户态/内核态切换;而自旋锁全程在用户态跑,只要临界区执行快、重试次数少,总延迟更低。
- 适用场景很窄:临界区最好在 100 纳秒级(比如简单计数器更新、flag 设置),且平均重试不超过 2–3 次
- 一旦出现中高竞争(比如 3+ 线程抢同一把锁),自旋会白占 CPU,
synchronized的阻塞反而更省资源 - JVM 对
synchronized有锁升级优化(偏向→轻量→重量),但在明确知道无竞争时,手写自旋锁仍可略胜一筹
容易被忽略的内存语义和 volatile 问题
AtomicReference 的读写天然具备 volatile 语义,但仅限它自己封装的字段。如果你在临界区内操作了其他普通变量,JVM 可能重排序,导致其他线程看到不一致状态。
- 必须确保所有共享状态的读写都发生在锁保护内 —— 不是“加了锁就安全”,而是“锁只保它自己,别的变量你得自己管”
- 别依赖
AtomicReference的get()去读业务数据:它只保证引用本身可见,不保证引用对象内部字段的可见性(除非对象本身是不可变的,或字段也声明为volatile) - 如果临界区要修改一个
int count,别把它声明为普通字段;要么用AtomicInteger,要么确保每次读写都通过同一个锁同步
别直接用在生产环境的三个硬伤
它没有可重入性、不可中断、不支持条件队列 —— 这三点让它的适用面远小于 synchronized 或 ReentrantLock。
- 同一线程重复
acquire()会死锁,因为第二次compareAndSet会拿Thread.currentThread()去比上一次存的相同值,永远失败 - 无法响应
Thread.interrupt(),一旦卡住只能等超时或进程结束 - 没法实现
wait()/notify()那类协作逻辑,所有等待都靠瞎转圈,业务耦合度极高
真要用,建议包装成带超时的 tryAcquire(long timeout, TimeUnit unit),并严格限定只用于极短、无嵌套、无等待的底层组件(比如 RingBuffer 的 cursor 更新)。否则,不如老老实实用 synchronized。










