atomicreference仅适用于单变量读-改-写原子操作且无复杂逻辑的场景,如计数器、状态标志位;不适用于多字段更新、含io/校验的业务或可变对象内部操作,因其仅保证引用替换原子性,非业务语义原子性。

AtomicReference 什么时候该用,什么时候不该用
它不是万能锁替代品,只在「单个变量读-改-写需原子性」且「不涉及复杂业务逻辑」时才合适。比如计数器、状态标志位、无锁栈顶指针。一旦要同时更新多个字段、或操作中需加日志/校验/远程调用,AtomicReference 就会掩盖问题——它保证的是引用本身替换的原子性,不是业务语义的原子性。
- 适合:状态机切换(
status.compareAndSet(RUNNING, STOPPING))、链表节点替换、轻量级缓存版本号 - 不适合:账户余额扣减(需检查余额再扣,中间可能被其他线程改);这种场景得用
while (!ref.compareAndSet(old, new)) old = ref.get();手动重试,但极易漏掉边界条件 - 常见错误:把
AtomicReference<list></list>当作线程安全集合用——ref.get().add(x)的add仍可能并发失败,AtomicReference只管“换不换得到这个 List 对象”,不管 List 内部怎么变
compareAndSet 和 getAndSet 的行为差异与选型
compareAndSet 是乐观策略:只在当前值等于预期值时才替换,返回 true 表示成功;getAndSet 是强制覆盖:不管当前值是什么,直接设新值并返回旧值。性能上两者都很快,但语义完全不同。
- 用
compareAndSet实现自旋锁或状态流转:必须配合循环重试,否则失败就丢数据 - 用
getAndSet实现“取走并重置”场景:比如清空一个待处理任务队列引用,同时拿到旧队列去消费 - 陷阱:误以为
compareAndSet(old, old)是 nop——它仍会触发 volatile 写屏障,且在高竞争下反复失败会浪费 CPU
AtomicReference 里 T 是可变对象时的典型误区
很多人以为把 AtomicReference<stringbuilder></stringbuilder> 包一层就线程安全了,其实只是“指向哪个 StringBuilder 对象”是原子的,ref.get().append("x") 这一行仍然不是线程安全的——多个线程可能同时调用 append 导致内容错乱或扩容异常。
- 正确做法:要么让
T是不可变类(如String、自定义的ImmutablePoint),要么把可变对象的操作封装进同步块或使用线程安全类型(如ConcurrentHashMap) - 调试线索:如果看到
ArrayIndexOutOfBoundsException或内容丢失,且堆栈里有StringBuilder.append或ArrayList.add,大概率是这个原因 - 注意:
AtomicReference的updateAndGet/accumulateAndGet方法只对函数式操作安全,前提是传入的UnaryOperator或BinaryOperator本身无副作用、不修改外部状态
和 synchronized / ReentrantLock 比,到底省了什么、又多了什么
省的是阻塞开销和上下文切换,多的是对使用者的逻辑要求:你得自己处理失败重试、避免 ABA 问题、确保操作幂等。没有自动的临界区保护,也没有内置的等待通知机制。
立即学习“Java免费学习笔记(深入)”;
- 性能敏感路径(如高频计数)用
AtomicReference更稳;但涉及 IO、锁粒度大、或需要条件等待时,synchronized更直白可靠 - ABA 问题真实存在:线程 A 读到值为 X,被挂起;线程 B 把 X→Y→X 改回去;A 恢复后
compareAndSet(X, Z)仍成功,但中间状态已丢失。需要用AtomicStampedReference加版本戳 - 别为了“看起来高级”而用原子类——只要临界区代码不超过 5 行、没嵌套调用、不涉及慢操作,
synchronized往往更少出错
真正难的从来不是调用哪个方法,而是想清楚“这一小段逻辑,到底需要哪一层的原子性”。AtomicReference 只担保引用层面的一次赋值,再多的,它不管。









