引用类型原子类
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<E> next;
Node(E value) {
this.value = value;
}
}
private final AtomicReference<Node<E>> top = new AtomicReference<>();
public void push(E value) {
Node<E> newNode = new Node<>(value);
Node<E> oldTop;
do {
oldTop = top.get();
newNode.next = oldTop;
} while (!top.compareAndSet(oldTop, newNode));
}
public E pop() {
Node<E> oldTop;
Node<E> 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<Integer> stack = new LockFreeStack<>();
// 入栈
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<String> ref =
new AtomicStampedReference<>("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<K, V> {
private static class CacheEntry<V> {
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<K, AtomicReference<CacheEntry<V>>> cache =
new ConcurrentHashMap<>();
public void put(K key, V value, long ttlMillis) {
AtomicReference<CacheEntry<V>> ref = cache.computeIfAbsent(
key, k -> new AtomicReference<>()
);
ref.set(new CacheEntry<>(value, ttlMillis));
}
public V get(K key) {
AtomicReference<CacheEntry<V>> ref = cache.get(key);
if (ref == null) {
return null;
}
CacheEntry<V> 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<String, String> cache = new ConcurrentCache<>();
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<String> ref =
new AtomicMarkableReference<>("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));
}
}适合的场景:删除标记——判断对象是否被删除过。
注意事项
- ABA 问题:AtomicReference 存在 ABA 问题,用 Stamped/Markable 版本解决
- 版本号递增:每次修改版本号应该 +1,而不是随意设置
- mark vs stamp:mark 是布尔值(简单),stamp 是整数值(精确)
- 泛型约束:泛型类型不能是具体的基本类型,必须是包装类或对象
选择指南
需要记录修改历史吗?
├── 是 ──► AtomicStampedReference(版本号递增)
└── 否 ──► 只需要"是否删除过"标记吗?
├── 是 ──► AtomicMarkableReference
└── 否 ──► AtomicReference