资讯动态

从AtomicReference到AtomicStampedReference:一次讲透Java原子类的ABA问题与解决方案

发布时间:2026/10/27 22:48:50 来源:尧图企业网站定制
从AtomicReference到AtomicStampedReference深入解析Java原子类的ABA问题与实战解决方案在并发编程的世界里原子操作就像黑暗中的灯塔为开发者提供了一种无需锁就能保证线程安全的可能性。Java的java.util.concurrent.atomic包正是这样一个灯塔群而AtomicReference无疑是其中最耀眼的几座之一。但当我们在高并发场景下深入使用时会发现这座灯塔有个微妙的盲区——ABA问题。今天我们就从实际案例出发彻底剖析这个问题并看看AtomicStampedReference如何成为我们的救星。1. AtomicReference的ABA问题表面平静下的暗流ABA问题就像是一个精心设计的魔术戏法你离开房间时桌上放着一个红苹果状态A回来后看到的还是一个红苹果状态A但实际上中间有人偷偷把它换成了青苹果状态B又换了回来。对于只关心最终状态的观察者来说一切如常但对于需要追踪变化过程的人来说这却是个严重的信息缺失。1.1 经典ABA问题场景让我们用代码还原这个魔术。假设我们有一个无锁栈实现public class ConcurrentStackT { private AtomicReferenceNodeT top new AtomicReference(); public void push(T item) { NodeT newHead new Node(item); NodeT oldHead; do { oldHead top.get(); newHead.next oldHead; } while (!top.compareAndSet(oldHead, newHead)); } public T pop() { NodeT oldHead; NodeT newHead; do { oldHead top.get(); if (oldHead null) return null; newHead oldHead.next; } while (!top.compareAndSet(oldHead, newHead)); return oldHead.item; } private static class NodeT { final T item; NodeT next; Node(T item) { this.item item; } } }现在考虑以下执行序列线程1调用pop()获取头节点A准备将其替换为BA.next线程2介入先pop()掉A再pop()掉B线程2又把A压回栈中线程1继续执行CAS操作发现top仍然是A于是成功将top设置为B此时栈的状态已经错乱但CAS操作却成功了。这就是典型的ABA问题。1.2 为什么这在实际中是个问题你可能觉得这种情况很极端但在以下场景中确实会发生内存回收系统对象被释放后又重新分配状态机实现状态从A→B→A的循环版本控制系统文件被修改后又被还原关键点当你的业务逻辑需要关心对象是否曾经被修改过而不仅仅是当前对象是什么时ABA问题就会成为隐患。2. 解决方案的演进从版本号到标记位既然问题的根源在于无法感知中间状态变化最直观的解决方案就是为每次修改添加一个版本号。这就是AtomicStampedReference的核心思想。2.1 AtomicStampedReference设计解析public class AtomicStampedReferenceV { private static class PairT { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference reference; this.stamp stamp; } static T PairT of(T reference, int stamp) { return new PairT(reference, stamp); } } private volatile PairV pair; public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { PairV current pair; return expectedReference current.reference expectedStamp current.stamp ((newReference current.reference newStamp current.stamp) || casPair(current, Pair.of(newReference, newStamp))); } }关键改进将引用和版本号打包成不可变的Pair对象CAS操作同时检查引用和版本号每次成功修改都会递增版本号2.2 改进后的无锁栈实现让我们用AtomicStampedReference修复之前的栈实现public class SafeConcurrentStackT { private AtomicStampedReferenceNodeT top new AtomicStampedReference(null, 0); public void push(T item) { NodeT newHead new Node(item); int[] stampHolder new int[1]; NodeT oldHead; do { oldHead top.get(stampHolder); newHead.next oldHead; } while (!top.compareAndSet(oldHead, newHead, stampHolder[0], stampHolder[0] 1)); } public T pop() { int[] stampHolder new int[1]; NodeT oldHead; NodeT newHead; do { oldHead top.get(stampHolder); if (oldHead null) return null; newHead oldHead.next; } while (!top.compareAndSet(oldHead, newHead, stampHolder[0], stampHolder[0] 1)); return oldHead.item; } }现在即使引用从A→B→A版本号也会从1→2→3CAS操作能准确感知到这种变化。3. 变体方案AtomicMarkableReference有时候我们不需要精确的版本号只需要知道是否有变化。这时AtomicMarkableReference就是更轻量的选择。3.1 适用场景对比特性AtomicReferenceAtomicStampedReferenceAtomicMarkableReference是否解决ABA问题❌✅✅额外存储开销无int(4字节)boolean(1字节)适用场景不关心历史变化需要完整版本历史只需知道是否被修改过典型应用简单状态标志无锁数据结构缓存失效标记3.2 代码示例缓存失效机制public class CacheK,V { private static class EntryV { final V value; final long expireTime; Entry(V value, long duration) { this.value value; this.expireTime System.currentTimeMillis() duration; } } private final AtomicMarkableReferenceMapK, EntryV cacheRef new AtomicMarkableReference(new ConcurrentHashMap(), false); public V get(K key) { MapK, EntryV cache cacheRef.getReference(); EntryV entry cache.get(key); if (entry ! null entry.expireTime System.currentTimeMillis()) { return entry.value; } return null; } public void invalidateAll() { MapK, EntryV current; boolean[] markHolder new boolean[1]; do { current cacheRef.get(markHolder); } while (!cacheRef.compareAndSet(current, current, markHolder[0], true)); } public void refresh() { boolean[] markHolder new boolean[1]; MapK, EntryV current; do { current cacheRef.get(markHolder); if (!markHolder[0]) break; } while (!cacheRef.compareAndSet(current, new ConcurrentHashMap(), true, false)); } }这个缓存实现使用标记位来指示缓存是否失效比版本号更节省内存同时满足了基本的ABA防护需求。4. 深入底层JVM如何保证这些操作的原子性所有这些原子类的魔法都源于同一个基础——CPU的CAS指令。现代处理器通常提供compare-and-swap这样的原子指令JVM通过Unsafe类暴露这些能力。4.1 HotSpot实现一瞥以x86架构为例AtomicStampedReference的CAS操作最终会编译为类似这样的汇编指令lock cmpxchg8b qword ptr [r10]其中lock前缀保证操作的原子性cmpxchg8b是比较并交换8字节足够容纳引用int版本号的指令操作的内存地址存放在r10寄存器中4.2 性能考量虽然原子类避免了锁的开销但在高竞争环境下仍可能遇到性能问题自旋消耗CPUCAS失败会重试导致忙等待伪共享频繁修改的原子变量可能引发缓存行无效化优化建议对于高度竞争的场景考虑LongAdder的分片思想使用Contended注解避免伪共享Java 8适当引入退避策略减少竞争public class Backoff { private final int minDelay, maxDelay; private int limit; public Backoff(int min, int max) { minDelay min; maxDelay max; limit minDelay; } public void backoff() throws InterruptedException { int delay ThreadLocalRandom.current().nextInt(limit); limit Math.min(maxDelay, limit * 2); Thread.sleep(delay); } } // 使用示例 public void contendedOperation(AtomicStampedReference? ref) { Backoff backoff new Backoff(1, 100); // 1-100ms退避 while (true) { // 尝试CAS操作 if (/* CAS成功 */) break; backoff.backoff(); } }5. 实战经验如何选择合适的原子类在真实项目中选择原子类时需要考虑以下维度5.1 决策树是否需要原子性否 → 使用普通变量是 → 2操作的是基本类型是 → 使用AtomicInteger等基本类型原子类否 → 3需要防止ABA问题否 →AtomicReference是 → 4需要完整版本历史是 →AtomicStampedReference否 →AtomicMarkableReference5.2 典型应用模式模式1状态机实现public class StateMachine { private AtomicStampedReferenceState stateRef; public void transition(State newState) { int[] stampHolder new int[1]; State current; do { current stateRef.get(stampHolder); if (!current.canTransitionTo(newState)) { throw new IllegalStateException(); } } while (!stateRef.compareAndSet(current, newState, stampHolder[0], stampHolder[0] 1)); } public boolean isInState(State expected) { return stateRef.getReference() expected; } }模式2乐观锁控制public class OptimisticLockingListT { private static class NodeT { final T item; final AtomicStampedReferenceNodeT next; Node(T item, NodeT next) { this.item item; this.next new AtomicStampedReference(next, 0); } } private final AtomicStampedReferenceNodeT head; public boolean add(T item) { NodeT newNode new Node(item, null); int[] stampHolder new int[1]; NodeT currentTail; NodeT next; do { currentTail head.get(stampHolder); if (currentTail null) { // 空列表 if (head.compareAndSet(null, newNode, 0, 1)) { return true; } } else { next currentTail.next.getReference(); if (next null) { if (currentTail.next.compareAndSet(null, newNode, currentTail.next.getStamp(), currentTail.next.getStamp() 1)) { return true; } } else { // 帮助推进尾节点 head.compareAndSet(currentTail, next, stampHolder[0], stampHolder[0] 1); } } } while (true); } }在实现这些模式时一个常见陷阱是过度依赖原子类。记住原子类最适合管理独立的共享变量对于复杂的多变量不变式还是需要考虑更高级的并发控制手段。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑