AtomicMarkableReference的布尔标记由用户自定义语义,标记引用指向对象是否被逻辑修改,需显式调用compareAndSet更新,不自动感知字段变化;底层原子校验引用与标记,适用于需绑定引用的双值CAS场景。

AtomicMarkableReference 的布尔标记到底标记什么
它标记的是引用指向的对象是否被逻辑上“修改过”,但这个“修改”完全由你定义,不是自动检测内容变化。底层用一个 boolean 和一个 Object 引用打包成 pair,CAS 操作同时检查二者——哪怕引用没变、只改了标记,也算一次有效更新。
常见错误是以为它能自动感知对象字段变更,比如对 AtomicMarkableReference<person></person> 修改了 person.name,却期待标记自动翻转——不会。你必须显式调用 compareAndSet 并传入新标记值。
- 标记语义完全由业务决定:可以表示“已初始化”“已被删除”“版本已过期”
- 如果只关心“是否变过”,建议初始化时设
false,首次成功 CAS 时设为true,之后不再改标记 - 不要把标记和对象状态混为一谈;
get()返回的是当前引用,isMarked()才返回标记值
只关心是否被修改过:最简 CAS 写法
如果你真只需要“有没有动过”这个二值信号,没必要每次都生成新对象。用一个哑元对象(比如 NULL_PLACEHOLDER)做引用,靠标记翻转来记录“已触发”状态。
private static final Object NULL_PLACEHOLDER = new Object();
private final AtomicMarkableReference<Object> modified = new AtomicMarkableReference<>(NULL_PLACEHOLDER, false);
public boolean markAsModified() {
return modified.compareAndSet(NULL_PLACEHOLDER, NULL_PLACEHOLDER, false, true);
}
这段代码里,引用始终是 NULL_PLACEHOLDER,只靠标记从 false → true 表示“首次被修改”。后续再调用 markAsModified() 会失败,符合“只关心是否被修改过”的需求。
立即学习“Java免费学习笔记(深入)”;
- 不能用
null当占位符——AtomicMarkableReference允许引用为null,但会和“未设置”混淆,易出错 - 别在循环里无条件重试
compareAndSet:一旦标记翻成true,后续所有 CAS 都会失败,死循环 - 如果需要支持多次“重置后再次标记”,就得自己管理状态机,
AtomicMarkableReference本身不提供 reset 接口
和 AtomicBoolean 相比差在哪
表面看,AtomicBoolean 也能做到“标记是否修改过”,而且更轻量。但关键区别在于:它无法与某个关联对象的生命周期或引用绑定。
典型场景是无锁栈/队列中,既要更新节点引用,又要同步标记该节点是否被逻辑删除——这时必须用 AtomicMarkableReference,因为 compareAndSet 是原子的“引用+标记”双校验。而分开用 AtomicBoolean + 普通引用,会出现 ABA 问题或中间态不一致。
- 单点布尔状态 → 用
AtomicBoolean,简单直接 - 需要和某个对象引用强绑定、且要求 CAS 同时校验二者 → 必须用
AtomicMarkableReference -
AtomicMarkableReference内存开销略大(多存一个boolean字段),且get()返回的是Pair<V, Boolean>,需拆包
容易忽略的内存模型细节
AtomicMarkableReference 的所有操作都遵循 volatile 语义,但它的 weakCompareAndSet 方法(如果存在)不保证 happens-before——不过注意:Java 9+ 已弃用所有 weakCompareAndSet 变体,统一用 compareAndSet 或 weakCompareAndSetPlain。
真正容易踩坑的是:标记值的读取没有隐式屏障。比如你调用 isMarked() 得到 true,不代表此时 get() 一定能看到最新引用——虽然概率极低,但在严格顺序敏感场景下,应使用 get(boolean[]) 这个重载方法一次性取出引用和标记:
boolean[] marked = new boolean[1];
Object ref = atomicRef.get(marked);
if (marked[0]) {
// 此时 ref 和 marked[0] 是同一时刻快照
}
- 单独调用
get()和isMarked()可能跨多个指令,中间被其他线程修改 -
get(boolean[])是唯一能保证二者原子可见的方法,文档里藏得深,但很关键 - 如果只是做“是否修改过”的最终判断(比如日志、统计),用单独
isMarked()没问题;但若要据此分支执行不同引用操作,务必用带数组的版本










