用AtomicReference构建线程安全状态机需遵循不可变状态+原子引用+CAS跃迁协同设计:状态类须final字段、无setter、深不可变;用compareAndSet安全切换;校验后CAS,成功再执行副作用;ABA按需用版本号。

用 AtomicReference 构建线程安全的自定义状态机,关键不是“给类加个原子包装就完事”,而是围绕不可变状态 + 原子引用 + CAS跃迁三者协同设计。构造器本身不提供线程安全保证,真正起作用的是对象创建后通过 compareAndSet 安全发布和切换。
定义不可变的状态类
每个状态必须是不可变的(immutable),所有字段声明为 final,不提供修改方法,也不暴露可变内部对象。
- 推荐用 Java 14+ 的
record,它天然满足不可变语义,自动实现equals/hashCode,且构造过程清晰:
// 示例:订单的三种状态record Created(String orderId, long createTime) implements OrderState {}record Processing(String orderId, String workerId) implements OrderState {}record Completed(String orderId, LocalDateTime finishTime) implements OrderState {}
- 若需兼容老版本 JDK,可用 Lombok
@Value或手动写final字段 + 全参构造 + 无 setter; - 嵌套集合要深不可变:比如
final List<String> tags不够,应改为final List<String> tags = List.copyOf(input)或使用ImmutableList; - 构造中禁止泄露
this(如注册监听、启动线程),否则可能被其他线程拿到未初始化完成的对象。
用 AtomicReference 管理当前状态
声明一个 AtomicReference<OrderState>,只允许通过其原子方法更新引用,禁用普通赋值(如 state = new Created(...))。
立即学习“Java免费学习笔记(深入)”;
- 初始化时直接传入初始状态实例,避免 null 引用:
private final AtomicReference<OrderState> stateRef = new AtomicReference<>(new Created("ORD-001", System.currentTimeMillis()));
- 所有状态变更都走
compareAndSet(expected, next),且expected必须是当前实际持有的引用(不能每次 new 一个内容相同但地址不同的对象); - 不要用
weakCompareAndSet—— 它不保证内存顺序,日常业务中应一律使用compareAndSet。
封装带条件的状态跃迁逻辑
状态流转不是无脑替换,必须按业务规则校验当前状态和上下文,再执行 CAS。判断逻辑必须放在 compareAndSet 调用之前,且不依赖共享可变变量。
- 例如:“仅当当前是
Created状态,且订单金额大于 0,才允许进入Processing”:
boolean startProcessing(String workerId) {<br> OrderState current = stateRef.get();<br> if (current instanceof Created created && getOrderAmount(created.orderId()) > 0) {<br> Processing nextState = new Processing(created.orderId(), workerId);<br> return stateRef.compareAndSet(current, nextState);<br> }<br> return false;<br>}
- 如果一次 CAS 失败(比如其他线程抢先更新了状态),方法直接返回 false,由调用方决定重试、报错或降级;
- 避免在 CAS 前做耗时操作(如远程调用、DB 查询),否则会拉长临界窗口,降低并发效率;
- 副作用(如发消息、记日志)应放在 CAS 成功之后,确保只在状态真实变更时触发。
处理 ABA 问题(按需)
普通状态机多数场景无需考虑 ABA,因为状态对象本身是不可变的,两次 Created 实例即使字段相同,也是不同引用,CAS 自然失败。但若业务允许“退回原状态再前进”且需严格区分中间变更,可用 AtomicStampedReference 加版本号。
- 例如:订单状态从
Created → Processing → Created,再想进Processing时需知道它已被处理过一次; - 此时把状态和整数版本号一起封装,每次状态变更都递增 stamp,CAS 变成四参数:
compareAndSet(oldState, newState, oldStamp, newStamp); - 注意:引入 stamp 会增加复杂度,除非明确需要检测“是否被改过”,否则不建议默认启用。


















