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 来封装引用和标记
  • referencemark 都是 final,不可变
  • 所有更新操作都会创建新的 Pair 实例,通过 CAS 替换旧的 pair 引用

✅ 这种“不可变对象 + volatile 引用 + CAS”模式是 Java 并发包中常见的无锁设计套路,比如 AtomicReferenceConcurrentHashMap 都有类似思想。

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:当前员工对象
  • marktrue 表示在职,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 地址,实际项目请替换为真实仓库)


原始标题:Guide to AtomicMarkableReference