java.util.concurrent.atomic 不只有 AtomicInteger。按被更新的数据来看,可以分成基本类型、数组、引用和字段更新器几组。
基本类型与数组
AtomicInteger、AtomicLong、AtomicBoolean 分别用于对应值的原子访问。getAndIncrement() 返回旧值,incrementAndGet() 返回更新后的值,这个顺序很容易看反。
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicValueDemo {
public static void main(String[] args) {
AtomicInteger value = new AtomicInteger(10);
System.out.println(value.getAndIncrement());
System.out.println(value.incrementAndGet());
int[] original = {1, 2};
AtomicIntegerArray array = new AtomicIntegerArray(original);
array.addAndGet(0, 5);
System.out.println(array.get(0));
System.out.println(original[0]);
}
}依次输出 10、12、6、1。通过数组构造 AtomicIntegerArray 时,会复制传入的内容,不是直接把原数组包装成原子数组。
每个元素的操作是原子的,但连续更新两个元素仍是两次操作。需要维持整个数组的一致快照时,要另外设计同步方式。
原子引用
AtomicReference<T> 更新的是引用本身,适合整体替换一个状态对象。它不会让对象内部所有字段都自动线程安全。
例如状态同时包含数量和金额,可以先根据旧状态构建一个新的不可变对象,再 CAS 替换引用。失败后重新读取和计算。不要在替换前直接修改旧对象,否则其他线程早就能看到那些修改,CAS 失败也无法撤回。
带版本和标记的引用分别是 AtomicStampedReference、AtomicMarkableReference,用于需要额外状态参与比较的情况。
字段更新器
已有对象中只想原子更新某个字段,可以使用字段更新器:
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class FieldUpdaterDemo {
public volatile int count;
private static final AtomicIntegerFieldUpdater<FieldUpdaterDemo> UPDATER =
AtomicIntegerFieldUpdater.newUpdater(FieldUpdaterDemo.class, "count");
public static void main(String[] args) {
FieldUpdaterDemo demo = new FieldUpdaterDemo();
System.out.println(UPDATER.incrementAndGet(demo));
System.out.println(demo.count);
}
}输出两次 1。更新器依赖字段名称和访问权限;整数字段要是 volatile 的 int,不是 Integer,也不能是 static 字段。
这种方式可以让很多对象共用一个更新器,减少每个对象再包装一个原子对象的需要。但所有复合更新都必须遵守同一套规则,其他代码直接写 count++,依然会破坏正确性。
不要把原子操作拼成非原子流程
先 get() 判断库存大于零,再 decrementAndGet(),两步之间可能已经被其他线程修改。原子类保证的是具体方法的操作,不是任意几行代码组合后的业务流程。
可以用 CAS 循环实现带条件的扣减,也可以用锁。涉及多个对象、多项检查时,锁往往更容易表达完整约束。