1. 概述
本文深入解析 Java 并发包 java.util.concurrent.atomic
中的 AtomicMarkableReference
类。
我们会梳理它的核心 API 方法,并结合实际场景演示它的典型用法。如果你在高并发编程中遇到过 ABA 问题,或者需要原子性地维护一个引用及其状态标记,那这篇文章值得集合。
2. 设计目的
AtomicMarkableReference
是一个泛型类,它将一个对象引用(reference)和一个布尔标记(mark)捆绑在一起,并支持对这两个字段进行原子性更新——可以同时更新,也可以只更新其中一个。
✅ 核心用途:
- 原子化管理“引用 + 状态”的组合
- 作为解决 ABA 问题的一种方案(相比
AtomicReference
更进一步)
⚠️ 注意:它和 AtomicStampedReference
类似,但后者用的是 int
版本号,而前者只用一个 boolean
标记,适用场景更简单。
3. 实现原理
先看下 AtomicMarkableReference
的底层结构:
public class AtomicMarkableReference<V> {
private static class Pair<T> {
final T reference;
final boolean mark;
private Pair(T reference, boolean mark) {
this.reference = reference;
this.mark = mark;
}
static <T> Pair<T> of(T reference, boolean mark) {
return new Pair<T>(reference, mark);
}
}
private volatile Pair<V> pair;
// ...
}
关键点如下:
- 使用一个私有的静态内部类
Pair
来封装引用和标记 reference
和mark
都是final
,不可变- 所有更新操作都会创建新的
Pair
实例,通过 CAS 替换旧的pair
引用
✅ 这种“不可变对象 + volatile 引用 + CAS”模式是 Java 并发包中常见的无锁设计套路,比如 AtomicReference
、ConcurrentHashMap
都有类似思想。
4. 核心方法详解
为了演示方便,先定义一个简单的 Employee
类:
class Employee {
private int id;
private String name;
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
// getter and setter
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return id == employee.id && name.equals(employee.name);
}
@Override
public int hashCode() {
return id;
}
}
假设我们用 AtomicMarkableReference<Employee>
表示组织架构中的一个员工节点:
reference
:当前员工对象mark
:true
表示在职,false
表示已离职
4.1 getReference()
获取当前引用对象:
Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);
Assertions.assertEquals(employee, employeeNode.getReference());
简单粗暴,就是返回当前 reference。
4.2 isMarked()
获取当前标记值:
Assertions.assertTrue(employeeNode.isMarked());
判断员工是否“标记”了(比如在职状态)。
4.3 get()
同时获取 reference 和 mark。注意 API 设计有点反直觉:
boolean[] markHolder = new boolean[1];
Employee currentEmployee = employeeNode.get(markHolder);
Assertions.assertEquals(employee, currentEmployee);
Assertions.assertTrue(markHolder[0]);
⚠️ 为什么用数组传 mark?因为 Java 没有提供公共的 Pair<T, U>
类型(避免滥用),所以这里用 boolean[1]
作为“输出参数”来带回 mark 值。
虽然写法略丑,但这是标准做法,别自己造轮子。
4.4 set()
无条件设置 reference 和 mark:
Employee newEmployee = new Employee(124, "John");
employeeNode.set(newEmployee, false);
Assertions.assertEquals(newEmployee, employeeNode.getReference());
Assertions.assertFalse(employeeNode.isMarked());
✅ 适用场景:你不需要条件判断,直接覆盖当前值。
4.5 compareAndSet()
条件更新——只有当当前 reference 和 mark 都匹配预期值时,才更新为新值:
Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);
Employee newEmployee = new Employee(124, "John");
Assertions.assertTrue(employeeNode.compareAndSet(employee, newEmployee, true, false));
Assertions.assertEquals(newEmployee, employeeNode.getReference());
Assertions.assertFalse(employeeNode.isMarked());
返回值:
- ✅
true
:更新成功 - ❌
false
:当前值不匹配,更新失败
这是实现无锁并发的核心方法,常用于 retry-loop 场景。
4.6 weakCompareAndSet()
理论上是 compareAndSet
的“弱版本”,不保证强内存顺序,且可能伪失败(spuriously fail)。
但重点来了 ⚠️:
目前 OpenJDK 的实现中,
weakCompareAndSet
**底层直接调用了compareAndSet
**,行为完全一致。
源码证据:
// OpenJDK 实现
public boolean weakCompareAndSet(V expectedReference,
V newReference,
boolean expectedMark,
boolean newMark) {
return compareAndSet(expectedReference, newReference, expectedMark, newMark);
}
所以:
- ✅ 理论上弱版本可能更轻量(某些平台)
- ❌ 但当前 JVM 实现没区别
- 💡 建议:**优先使用
compareAndSet
**,除非你明确知道weakCompareAndSet
在你的场景下更优
4.7 attemptMark()
只更新 mark 标记,但前提是 reference 匹配预期值:
Assertions.assertTrue(employeeNode.attemptMark(employee, false));
Assertions.assertFalse(employeeNode.isMarked());
⚠️ 注意:
- 它也可能伪失败(即使 reference 匹配)
- 返回
false
不代表失败,可能是 CAS 竞争导致 - 推荐在 while 循环中使用,确保最终成功
典型用法:
while (!employeeNode.attemptMark(expectedEmployee, false)) {
// retry until success
}
这其实是 compareAndSet
的一种简化封装,适用于“只关心 reference 是否变化,然后更新 mark”的场景。
5. 总结
AtomicMarkableReference
的核心价值在于:
✅ 原子性维护“引用 + 布尔标记”
✅ 简化 ABA 问题的处理(比 AtomicReference
更安全)
✅ 适合状态标记类场景(如:节点是否删除、资源是否可用)
但也要注意:
- 如果需要更复杂的状态版本控制,考虑
AtomicStampedReference
weakCompareAndSet
当前和compareAndSet
无区别,别被文档误导- API 设计略别扭(如
get(boolean[])
),但理解原理后就能接受
示例代码已整理至 GitHub:https://github.com/baomidou-concurrent-demo
(mock 地址,实际项目请替换为真实仓库)