Skip to content

引用类型原子类

AtomicInteger 只能操作 int 值,如果我想原子地更新一个对象引用呢?

AtomicReference 就是干这个的——它让你对引用类型进行无锁的原子操作。

三剑客

用途特点
AtomicReference原子更新引用最基础
AtomicStampedReference带版本号的引用解决 ABA
AtomicMarkableReference带标记的引用变种 ABA

AtomicReference 基础用法

java
public class AtomicReferenceDemo {

    private static final AtomicReference<String> ref =
        new AtomicReference<>("初始值");

    public static void main(String[] args) {
        // get()
        System.out.println("get(): " + ref.get());

        // set()
        ref.set("新值");
        System.out.println("set() 后: " + ref.get());

        // getAndSet()
        String old = ref.getAndSet("另一个值");
        System.out.println("getAndSet() 返回旧值: " + old);
        System.out.println("当前值: " + ref.get());

        // compareAndSet()
        boolean success = ref.compareAndSet("另一个值", "CAS设置");
        System.out.println("compareAndSet() 结果: " + success);
        System.out.println("最终值: " + ref.get());
    }
}

无锁数据结构:链表栈

这是 AtomicReference 最经典的应用——实现无锁的并发数据结构。

java
public class LockFreeStack<E> {

    private static class Node<E> {
        E value;
        volatile Node&lt;E&gt; next;

        Node(E value) {
            this.value = value;
        }
    }

    private final AtomicReference<Node&lt;E&gt;&gt; top = new AtomicReference&lt;&gt;();

    public void push(E value) {
        Node&lt;E&gt; newNode = new Node&lt;&gt;(value);
        Node&lt;E&gt; oldTop;
        do {
            oldTop = top.get();
            newNode.next = oldTop;
        } while (!top.compareAndSet(oldTop, newNode));
    }

    public E pop() {
        Node&lt;E&gt; oldTop;
        Node&lt;E&gt; newTop;
        do {
            oldTop = top.get();
            if (oldTop == null) {
                return null;
            }
            newTop = oldTop.next;
        } while (!top.compareAndSet(oldTop, newTop));

        return oldTop.value;
    }

    public static void main(String[] args) {
        LockFreeStack&lt;Integer&gt; stack = new LockFreeStack&lt;&gt;();

        // 入栈
        stack.push(1);
        stack.push(2);
        stack.push(3);

        // 出栈
        System.out.println(stack.pop());  // 3
        System.out.println(stack.pop());  // 2
        System.out.println(stack.pop());  // 1
        System.out.println(stack.pop());  // null
    }
}

AtomicStampedReference:解决 ABA 问题

ABA 问题是这样的:

线程A: 读取 value = A
线程B: A → B → A(值变了两下又变回来了)
线程A: compareAndSet(A, C) 成功了!

但线程A 并不知道中间发生了什么。

StampedReference 的解决方案:加一个版本号,每次修改版本号 +1。

java
public class AtomicStampedReferenceDemo {

    public static void main(String[] args) {
        AtomicStampedReference&lt;String&gt; ref =
            new AtomicStampedReference&lt;&gt;("A", 0);

        int[] stamp = new int[1];

        // 获取当前值和版本号
        String current = ref.get(stamp);
        System.out.println("初始值: " + current + ", 版本: " + stamp[0]);  // A, 0

        // 修改值和版本号
        ref.compareAndSet("A", "B", stamp[0], stamp[0] + 1);
        System.out.println("修改后: " + ref.get(stamp));  // B, 1

        // 模拟 ABA:值变回 A,但版本号已经变了
        ref.compareAndSet("B", "A", stamp[0], stamp[0] + 1);
        System.out.println("ABA后: " + ref.get(stamp));  // A, 2

        // 用旧版本号(0) CAS 会失败
        boolean success = ref.compareAndSet("A", "C", 0, 10);
        System.out.println("用旧版本号 CAS: " + success);  // false
        System.out.println("当前值: " + ref.get(stamp));   // A, 2
    }
}

实战:并发缓存

java
public class ConcurrentCache&lt;K, V&gt; {

    private static class CacheEntry&lt;V&gt; {
        V value;
        volatile long expireTime;

        CacheEntry(V value, long ttl) {
            this.value = value;
            this.expireTime = System.currentTimeMillis() + ttl;
        }

        boolean isExpired() {
            return System.currentTimeMillis() > expireTime;
        }
    }

    private final Map&lt;K, AtomicReference&lt;CacheEntry&lt;V&gt;&gt;&gt; cache =
        new ConcurrentHashMap&lt;&gt;();

    public void put(K key, V value, long ttlMillis) {
        AtomicReference&lt;CacheEntry&lt;V&gt;&gt; ref = cache.computeIfAbsent(
            key, k -> new AtomicReference&lt;&gt;()
        );
        ref.set(new CacheEntry&lt;&gt;(value, ttlMillis));
    }

    public V get(K key) {
        AtomicReference&lt;CacheEntry&lt;V&gt;&gt; ref = cache.get(key);
        if (ref == null) {
            return null;
        }

        CacheEntry&lt;V&gt; entry = ref.get();
        if (entry == null || entry.isExpired()) {
            // CAS 删除过期条目
            ref.compareAndSet(entry, null);
            return null;
        }
        return entry.value;
    }

    public static void main(String[] args) throws InterruptedException {
        ConcurrentCache&lt;String, String&gt; cache = new ConcurrentCache&lt;&gt;();

        cache.put("key1", "value1", 1000);  // 1秒过期

        System.out.println("读取: " + cache.get("key1"));  // value1

        Thread.sleep(1500);
        System.out.println("过期后: " + cache.get("key1"));  // null
    }
}

AtomicMarkableReference:简化版版本控制

如果不需要递增的版本号,只需要一个布尔标记,用这个更简单:

java
public class AtomicMarkableReferenceDemo {

    public static void main(String[] args) {
        AtomicMarkableReference&lt;String&gt; ref =
            new AtomicMarkableReference&lt;&gt;("A", false);

        boolean[] mark = new boolean[1];

        // 获取值和标记
        String current = ref.get(mark);
        System.out.println("当前值: " + current + ", 标记: " + mark[0]);

        // 修改值,保持标记
        ref.compareAndSet("A", "B", mark[0], true);
        System.out.println("修改后: " + ref.get(mark));

        // 修改标记
        ref.compareAndSet("B", "C", true, false);
        System.out.println("修改标记后: " + ref.get(mark));
    }
}

适合的场景:删除标记——判断对象是否被删除过。

注意事项

  1. ABA 问题:AtomicReference 存在 ABA 问题,用 Stamped/Markable 版本解决
  2. 版本号递增:每次修改版本号应该 +1,而不是随意设置
  3. mark vs stamp:mark 是布尔值(简单),stamp 是整数值(精确)
  4. 泛型约束:泛型类型不能是具体的基本类型,必须是包装类或对象

选择指南

需要记录修改历史吗?
  ├── 是 ──► AtomicStampedReference(版本号递增)
  └── 否 ──► 只需要"是否删除过"标记吗?
              ├── 是 ──► AtomicMarkableReference
              └── 否 ──► AtomicReference

基于 VitePress 构建