在并发编程中管理共享状态,许多开发者首先会考虑使用锁机制。然而,当状态本身可以简化为整型数值时——例如初始化、运行中、已暂停等离散阶段——AtomicInteger 便展现出其独特价值。它不仅是高效的计数器,更是构建轻量级、无锁状态机的理想工具。

其适用场景非常明确:状态可用整数编码、状态转换逻辑相对简单、且每次变更仅需一次原子性的“比较并交换”操作。满足这些条件时,采用 AtomicInteger 实现的代码将兼具简洁性与高性能。
状态定义与取值规范
首要步骤是将业务状态清晰地映射为整数值。例如,定义 0 为初始化(INIT),1 为运行中(RUNNING),依此类推。关键在于确保状态值互斥且唯一,避免使用单个整数的不同比特位同时表示多个标志位——那是 AtomicIntegerFieldUpdater 或基于 volatile int 加锁方案的应用领域,不应混淆。
实践中,有以下几点建议:
- 务必使用
public static final int常量明确定义每个状态,彻底消除代码中的“魔法数字”。 - 状态数量建议控制在10个以内。过多的状态会导致状态转换图变得复杂,难以理解和维护。
- 状态值应专用于表示状态,避免承载“版本号”或“时间戳”等额外语义,这些职责应由
Long类型或其他独立字段承担。
原子状态转换的核心实现
实现状态转换时,应避免先调用 get()、再进行判断、最后执行 set() 的非原子操作序列。在并发环境下,这会导致竞态条件。正确的方法是直接使用原子操作 compareAndSet(expect, update)。
例如,若要求只能从 INIT 状态转换到 RUNNING 状态,并防止重复启动,代码应如下编写:
if (status.compareAndSet(INIT, RUNNING)) {
// 转换成功:执行对应的初始化逻辑
doInitialize();
} else {
// 转换失败:说明当前已非INIT状态,可能已被其他线程抢先修改
throw new IllegalStateException("Invalid state transition");
}
可以看到,整个 if 块是线程安全的。compareAndSet 是CPU指令级别的原子操作,从根本上避免了“读取时是INIT,准备写入时已被他人改为RUNNING”这类竞态问题。
支持多条件转换与链式流转
实际场景往往更为复杂。例如,STOPPED(停止)状态可能允许从 RUNNING 或 PAUSED(暂停)状态进入。此时,单次 compareAndSet 无法满足需求,需要引入循环尝试机制,即CAS自旋。
int current;
do {
current = status.get();
if (current == RUNNING || current == PAUSED) {
if (status.compareAndSet(current, STOPPED)) {
break; // 转换成功,跳出循环
}
// 如果CAS失败,说明current值在获取后又被其他线程修改,循环重试
} else {
throw new IllegalStateException("Cannot stop from state: " + current);
}
} while (true);
这种结构适用于状态转换条件有限、且并发冲突不频繁的场景。如果状态分支极其复杂,或状态转换过程涉及外部I/O操作,则建议退回到使用锁保护的临界区方案,以确保逻辑清晰与可控性。
注意内存可见性与延迟写入边界
AtomicInteger 的 compareAndSet、get 等方法均具备 volatile 变量的内存语义,能确保状态变更对所有线程立即可见,这是一大优势。
但需注意一个细节:如果使用 lazySet 方法设置最终状态(例如标记为 TERMINATED),需知它不提供完整的“释放屏障”,其他线程可能在极短时间内读取到旧值。
总结如下:
- 基于
status.get()的状态判断逻辑(如if (status.get() == RUNNING))是可靠的,因为get()是volatile读。 - 设置最终状态时需谨慎使用
lazySet,除非你明确了解并能接受纳秒至微秒级的可见性延迟。 - 在
compareAndSet成功后,不要立即依赖其他非volatile字段的值,因为它们的内存可见性未必已同步更新。
总而言之,技术选型需契合场景。AtomicInteger 实现状态机的优势在于轻量、无锁、高性能,尤其适用于状态转换迅速、竞争程度较低的内部组件。正确运用此模式,可使代码既简洁又健壮。
