原子类
原子类是 JUC 提供的一组基于 CAS(Compare-And-Swap)实现的线程安全操作类。与使用锁相比,原子类采用无锁算法,在低竞争场景下性能更优。
CAS 原理
CAS(Compare-And-Swap)是一种乐观锁机制,是原子类实现无锁线程安全的基石。理解 CAS 原理,对于正确使用原子类和理解并发编程至关重要。
CAS 操作的语义
CAS 操作包含三个操作数:
- V(Value):内存位置,要更新的变量的地址
- E(Expected):预期值,线程认为该变量当前应该拥有的值
- N(New):新值,希望设置的新值
CAS 操作的语义是:当且仅当内存位置 V 的当前值等于预期值 E 时,才将 V 的值更新为 N。整个过程是原子的,不可分割。
用伪代码表示:
boolean CAS(V, E, N) {
if (V == E) {
V = N;
return true;
}
return false;
}
关键点在于这个操作是由硬件保证原子的。在多线程环境下,即使多个线程同时执行 CAS,也只有一个能成功,不会出现数据损坏。
底层实现机制
Java 中的 CAS 操作依赖于 sun.misc.Unsafe 类,这是一个提供低级别操作的内部 API。Unsafe 类中的 CAS 方法是 native 方法,最终调用 CPU 的 CAS 指令。
x86 架构的实现:
在 x86 处理器上,CAS 操作通过 cmpxchg 指令实现。在多核处理器环境下,这个指令需要配合 lock 前缀使用,以确保原子性:
lock cmpxchg [destination], source
lock 前缀的作用:
- 锁定总线或缓存行,防止其他 CPU 核心同时访问同一内存位置
- 确保整个比较和交换操作不可分割
- 提供内存屏障语义,保证可见性
Java 的调用链:
AtomicInteger.compareAndSet()
└── Unsafe.compareAndSwapInt()
└── JVM 内部 native 方法
└── CPU cmpxchg 指令
Unsafe 类简介:
Unsafe 类提供了直接操作内存的能力,包括:
public final class Unsafe {
// CAS 操作
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
// 内存屏障
public native void loadFence();
public native void storeFence();
public native void fullFence();
// volatile 读写
public native int getIntVolatile(Object o, long offset);
public native void putIntVolatile(Object o, long offset, int x);
}
Unsafe 类的设计初衷是供 JDK 内部使用,普通应用不应该直接使用。从 Java 9 开始,Unsafe 的大部分功能被 VarHandle API 替代。
AtomicInteger 的 CAS 实现
让我们看看 AtomicInteger 是如何使用 CAS 的:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final Unsafe U = Unsafe.getUnsafe();
private static final long VALUE;
static {
try {
// 获取 value 字段在对象中的内存偏移量
VALUE = U.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
private volatile int value;
public final boolean compareAndSet(int expectedValue, int newValue) {
// 调用 Unsafe 的 CAS 方法
return U.compareAndSet(this, VALUE, expectedValue, newValue);
}
public final int getAndIncrement() {
// 自旋 + CAS 实现
return U.getAndAddInt(this, VALUE, 1);
}
}
getAndAddInt 的实现展示了 CAS 的典型使用模式——自旋:
// Unsafe.getAndAddInt 的实现
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset); // 读取当前值
} while (!compareAndSetInt(o, offset, v, v + delta)); // CAS 失败则重试
return v;
}
这种"读取-计算-CAS-失败重试"的模式称为自旋锁或乐观锁。如果竞争不激烈,CAS 通常能在几次尝试内成功。
CAS vs 锁
理解 CAS 和锁的区别有助于选择合适的并发策略:
public class CounterComparison {
private int countWithLock = 0;
private AtomicInteger countWithCAS = new AtomicInteger(0);
private final Object lock = new Object();
public void incrementWithLock() {
synchronized (lock) {
countWithLock++;
}
}
public void incrementWithCAS() {
countWithCAS.incrementAndGet();
}
}
锁的特点:
- 悲观策略:假设会发生冲突,先加锁再操作
- 线程会被阻塞,涉及操作系统调度
- 可能导致死锁
- 锁竞争激烈时,线程挂起和唤醒的开销较大
- 可以实现更复杂的同步逻辑
CAS 的特点:
- 乐观策略:假设不会冲突,尝试更新,失败则重试
- 线程不会阻塞,始终在运行
- 不会死锁
- 低竞争时性能很好,高竞争时可能 CPU 占用高
- 只能保证单个变量的原子性
性能对比:
低竞争场景:CAS > 锁(无上下文切换开销)
中等竞争场景:CAS ≈ 锁
高竞争场景:锁 > CAS(CAS 频繁重试消耗 CPU)
CAS 的三个问题
虽然 CAS 很强大,但它也有局限性:
1. ABA 问题
ABA 问题是指一个值原来是 A,变成了 B,又变回了 A。CAS 检查时发现值仍然是 A,认为没有变化,实际上已经发生了变化。
线程1: 读取值 A → 准备 CAS → [暂停]
线程2: 将值改为 B
线程2: 将值改回 A
线程1: [恢复] CAS(A, A, N) 成功!
大多数情况下 ABA 问题不会造成影响,但在某些场景下可能导致问题。例如链表头节点被删除后又添加回来,CAS 操作可能成功但链表结构已经改变。
解决方案是使用版本号或时间戳,JUC 提供了 AtomicStampedReference 来解决此问题。
2. 循环时间长开销大
在高竞争场景下,如果多个线程同时修改同一变量,CAS 可能会失败多次,导致 CPU 空转。
解决方案:
- 使用
LongAdder替代AtomicLong,通过分散热点减少竞争 - 减少共享变量的竞争,使用线程本地变量
3. 只能保证一个共享变量的原子性
CAS 只能对单个变量进行原子操作,无法同时原子地操作多个变量。
解决方案:
- 将多个变量封装成一个对象,使用
AtomicReference - 使用锁
- 使用 VarHandle 进行更细粒度的控制
ABA 问题
ABA 问题是指一个值原来是 A,变成了 B,又变回了 A。CAS 检查时发现值仍然是 A,认为没有变化,实际上已经发生了变化。
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo {
public static void main(String[] args) {
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0);
new Thread(() -> {
int stamp = ref.getStamp();
System.out.println("线程1 读取值: " + ref.getReference() + ", 版本: " + stamp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean success = ref.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println("线程1 CAS 结果: " + success);
}).start();
new Thread(() -> {
int stamp = ref.getStamp();
ref.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println("线程2 第一次修改: " + ref.getReference() + ", 版本: " + ref.getStamp());
ref.compareAndSet(101, 100, ref.getStamp(), ref.getStamp() + 1);
System.out.println("线程2 第二次修改: " + ref.getReference() + ", 版本: " + ref.getStamp());
}).start();
}
}
AtomicStampedReference 通过版本号解决了 ABA 问题。
基本类型原子类
AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerDemo {
public static void main(String[] args) throws InterruptedException {
AtomicInteger count = new AtomicInteger(0);
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
count.incrementAndGet();
}
});
}
for (Thread t : threads) t.start();
for (Thread t : threads) t.join();
System.out.println("最终结果: " + count.get());
}
}
核心方法:
| 方法 | 说明 |
|---|---|
| get() | 获取当前值 |
| set(int) | 设置值 |
| getAndSet(int) | 设置新值,返回旧值 |
| compareAndSet(int, int) | CAS 操作 |
| getAndIncrement() | 自增,返回旧值 |
| incrementAndGet() | 自增,返回新值 |
| getAndDecrement() | 自减,返回旧值 |
| decrementAndGet() | 自减,返回新值 |
| getAndAdd(int) | 加指定值,返回旧值 |
| addAndGet(int) | 加指定值,返回新值 |
| updateAndGet(IntUnaryOperator) | 函数式更新 |
| getAndUpdate(IntUnaryOperator) | 函数式更新,返回旧值 |
函数式更新:
AtomicInteger count = new AtomicInteger(10);
int result = count.updateAndGet(x -> x * 2);
System.out.println(result); // 20
result = count.getAndUpdate(x -> x + 5);
System.out.println(result); // 20
System.out.println(count.get()); // 25
AtomicLong
与 AtomicInteger 类似,用于操作 long 类型:
import java.util.concurrent.atomic.AtomicLong;
public class AtomicLongDemo {
public static void main(String[] args) {
AtomicLong counter = new AtomicLong(0);
long result = counter.incrementAndGet();
System.out.println(result); // 1
result = counter.addAndGet(100);
System.out.println(result); // 101
}
}
AtomicBoolean
import java.util.concurrent.atomic.AtomicBoolean;
public class AtomicBooleanDemo {
private static AtomicBoolean initialized = new AtomicBoolean(false);
public static void init() {
if (initialized.compareAndSet(false, true)) {
System.out.println("执行初始化");
} else {
System.out.println("已经初始化过了");
}
}
public static void main(String[] args) {
init(); // 执行初始化
init(); // 已经初始化过了
init(); // 已经初始化过了
}
}
引用类型原子类
AtomicReference
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceDemo {
public static void main(String[] args) {
AtomicReference<User> userRef = new AtomicReference<>(new User("张三", 20));
User oldUser = userRef.get();
User newUser = new User("李四", 25);
boolean success = userRef.compareAndSet(oldUser, newUser);
System.out.println("更新结果: " + success);
System.out.println("当前用户: " + userRef.get());
userRef.updateAndGet(u -> new User(u.name(), u.age() + 1));
System.out.println("年龄+1后: " + userRef.get());
}
}
record User(String name, int age) {}
AtomicStampedReference
解决 ABA 问题的原子引用类,通过版本号标记:
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceDemo {
public static void main(String[] args) {
AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
int[] stampHolder = new int[1];
String value = ref.get(stampHolder);
int stamp = stampHolder[0];
System.out.println("当前值: " + value + ", 版本: " + stamp);
boolean success = ref.compareAndSet("A", "B", stamp, stamp + 1);
System.out.println("第一次更新: " + success);
success = ref.compareAndSet("A", "B", stamp, stamp + 1);
System.out.println("第二次更新(版本不匹配): " + success);
value = ref.get(stampHolder);
System.out.println("当前值: " + value + ", 版本: " + stampHolder[0]);
}
}
AtomicMarkableReference
使用布尔标记而非版本号:
import java.util.concurrent.atomic.AtomicMarkableReference;
public class AtomicMarkableReferenceDemo {
public static void main(String[] args) {
AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("数据", false);
boolean[] markHolder = new boolean[1];
String value = ref.get(markHolder);
System.out.println("值: " + value + ", 标记: " + markHolder[0]);
ref.compareAndSet("数据", "新数据", false, true);
value = ref.get(markHolder);
System.out.println("值: " + value + ", 标记: " + markHolder[0]);
}
}
数组类型原子类
AtomicIntegerArray
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayDemo {
public static void main(String[] args) {
int[] array = {1, 2, 3, 4, 5};
AtomicIntegerArray atomicArray = new AtomicIntegerArray(array);
System.out.println("原始数组: " + array[0]); // 1
atomicArray.set(0, 10);
System.out.println("原子数组: " + atomicArray.get(0)); // 10
System.out.println("原始数组: " + array[0]); // 1(不受影响)
int oldValue = atomicArray.getAndIncrement(1);
System.out.println("索引1旧值: " + oldValue); // 2
System.out.println("索引1新值: " + atomicArray.get(1)); // 3
atomicArray.addAndGet(2, 100);
System.out.println("索引2加100后: " + atomicArray.get(2)); // 103
boolean success = atomicArray.compareAndSet(3, 4, 40);
System.out.println("CAS 结果: " + success); // true
System.out.println("索引3新值: " + atomicArray.get(3)); // 40
}
}
AtomicLongArray
与 AtomicIntegerArray 类似,用于 long 类型数组。
AtomicReferenceArray
import java.util.concurrent.atomic.AtomicReferenceArray;
public class AtomicReferenceArrayDemo {
public static void main(String[] args) {
AtomicReferenceArray<String> array = new AtomicReferenceArray<>(5);
array.set(0, "Hello");
array.set(1, "World");
String old = array.getAndSet(0, "Hi");
System.out.println("旧值: " + old); // Hello
System.out.println("新值: " + array.get(0)); // Hi
}
}
字段更新器
字段更新器可以对类的 volatile 字段进行原子更新,无需将整个类改为原子类。
AtomicIntegerFieldUpdater
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicFieldUpdaterDemo {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<Person> ageUpdater =
AtomicIntegerFieldUpdater.newUpdater(Person.class, "age");
Person person = new Person("张三", 20);
ageUpdater.incrementAndGet(person);
System.out.println("年龄: " + person.age); // 21
ageUpdater.compareAndSet(person, 21, 25);
System.out.println("年龄: " + person.age); // 25
}
}
class Person {
volatile String name;
volatile int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
注意事项:
- 字段必须是 volatile
- 字段必须是可见的(public 或同一包内)
- 只能是实例字段,不能是静态字段
AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class MultiFieldUpdaterDemo {
public static void main(String[] args) {
AtomicLongFieldUpdater<Account> balanceUpdater =
AtomicLongFieldUpdater.newUpdater(Account.class, "balance");
AtomicReferenceFieldUpdater<Account, String> statusUpdater =
AtomicReferenceFieldUpdater.newUpdater(Account.class, String.class, "status");
Account account = new Account(1000, "正常");
balanceUpdater.addAndGet(account, 500);
System.out.println("余额: " + account.balance); // 1500
statusUpdater.compareAndSet(account, "正常", "冻结");
System.out.println("状态: " + account.status); // 冻结
}
}
class Account {
volatile long balance;
volatile String status;
Account(long balance, String status) {
this.balance = balance;
this.status = status;
}
}
累加器
Java 8 引入了高性能累加器,在高并发场景下比 AtomicLong 性能更好。理解累加器的设计原理,有助于在正确的场景选择正确的工具。
LongAdder
LongAdder 是专门为高并发计数场景设计的累加器。在竞争激烈时,它比 AtomicLong 有数量级的性能优势。
import java.util.concurrent.atomic.LongAdder;
public class LongAdderDemo {
public static void main(String[] args) throws InterruptedException {
LongAdder adder = new LongAdder();
Thread[] threads = new Thread[100];
for (int i = 0; i < 100; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
adder.increment();
}
});
}
long start = System.currentTimeMillis();
for (Thread t : threads) t.start();
for (Thread t : threads) t.join();
long end = System.currentTimeMillis();
System.out.println("LongAdder 结果: " + adder.sum() + ", 耗时: " + (end - start) + "ms");
}
}
LongAdder 的设计原理
为什么 LongAdder 在高并发下比 AtomicLong 快?关键在于"分散热点"的设计思想。
AtomicLong 的问题:
当多个线程同时更新 AtomicLong 时,它们都在竞争同一个变量:
线程1: CAS 更新 value → 失败 → 重试 → 失败 → 重试 → ...
线程2: CAS 更新 value → 失败 → 重试 → 失败 → ...
线程3: CAS 更新 value → 成功
线程4: CAS 更新 value → 失败 → 重试 → ...
...
所有线程竞争同一个变量,CAS 失败后不断重试,CPU 资源被大量浪费在重试上。
LongAdder 的解决方案:
LongAdder 维护一个 base 变量和一个 Cell 数组,每个 Cell 是一个独立的累加单元:
不同线程更新不同的 Cell,减少了竞争:
线程1: 更新 Cell[0] → 成功(无竞争)
线程2: 更新 Cell[1] → 成功(无竞争)
线程3: 更新 Cell[2] → 成功(无竞争)
线程4: 更新 Cell[3] → 成功(无竞争)
读取时,将 base 和所有 Cell 的值累加得到最终结果。
Cell 的分配策略:
每个线程通过 线程ID & (cells.length - 1) 计算自己应该使用哪个 Cell。Cell 数组按需创建,初始时为 null,只在需要时才初始化。
// 简化的更新逻辑
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
// cells 不为空,或者更新 base 失败
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
何时使用 Cell:
- 低竞争时,直接更新 base 变量(类似 AtomicLong)
- 当 CAS 更新 base 失败时,才创建 Cell 数组
- 每个 Cell 也是通过 CAS 更新,但由于分散到多个 Cell,竞争大大降低
空间换时间:
LongAdder 的代价是更多的内存占用。每个 Cell 包含一个 long 值和一些填充字段(避免伪共享),在高并发场景下可能有多个 Cell。但内存开销相对于性能提升通常是值得的。
LongAdder vs AtomicLong 对比
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
public class PerformanceComparison {
public static void main(String[] args) throws InterruptedException {
int threadCount = 100;
int incrementPerThread = 100000;
// AtomicLong 测试
AtomicLong atomicLong = new AtomicLong(0);
long start = System.currentTimeMillis();
Thread[] threads1 = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads1[i] = new Thread(() -> {
for (int j = 0; j < incrementPerThread; j++) {
atomicLong.incrementAndGet();
}
});
}
for (Thread t : threads1) t.start();
for (Thread t : threads1) t.join();
long end = System.currentTimeMillis();
System.out.println("AtomicLong: " + atomicLong.get() + ", 耗时: " + (end - start) + "ms");
// LongAdder 测试
LongAdder adder = new LongAdder();
start = System.currentTimeMillis();
Thread[] threads2 = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads2[i] = new Thread(() -> {
for (int j = 0; j < incrementPerThread; j++) {
adder.increment();
}
});
}
for (Thread t : threads2) t.start();
for (Thread t : threads2) t.join();
end = System.currentTimeMillis();
System.out.println("LongAdder: " + adder.sum() + ", 耗时: " + (end - start) + "ms");
}
}
典型结果:
AtomicLong: 10000000, 耗时: 800ms
LongAdder: 10000000, 耗时: 150ms
在高并发场景下,LongAdder 可能比 AtomicLong 快 5 倍以上。
选择指南:
| 场景 | 推荐 |
|---|---|
| 低竞争,需要精确值 | AtomicLong |
| 高竞争计数,只关心最终结果 | LongAdder |
| 需要读取中间值且要精确 | AtomicLong |
| 需要更新后立即读取 | AtomicLong |
| 统计计数(如 QPS 统计) | LongAdder |
注意:LongAdder 的 sum() 方法返回的是调用时刻所有 Cell 值的快照,不是原子快照。在计算过程中,其他线程可能正在更新 Cell。对于大多数统计场景,这是可以接受的。
LongAccumulator
LongAccumulator 比 LongAdder 更灵活,可以指定累加函数:
import java.util.concurrent.atomic.LongAccumulator;
public class LongAccumulatorDemo {
public static void main(String[] args) throws InterruptedException {
LongAccumulator maxAccumulator = new LongAccumulator(Long::max, Long.MIN_VALUE);
LongAccumulator sumAccumulator = new LongAccumulator(Long::sum, 0);
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
final int value = i * 100;
threads[i] = new Thread(() -> {
maxAccumulator.accumulate(value);
sumAccumulator.accumulate(value);
});
}
for (Thread t : threads) t.start();
for (Thread t : threads) t.join();
System.out.println("最大值: " + maxAccumulator.get()); // 900
System.out.println("总和: " + sumAccumulator.get()); // 4500
}
}
DoubleAdder 和 DoubleAccumulator
import java.util.concurrent.atomic.DoubleAdder;
import java.util.concurrent.atomic.DoubleAccumulator;
public class DoubleAdderDemo {
public static void main(String[] args) {
DoubleAdder adder = new DoubleAdder();
adder.add(1.5);
adder.add(2.5);
System.out.println("总和: " + adder.sum()); // 4.0
DoubleAccumulator maxAcc = new DoubleAccumulator(Double::max, Double.NEGATIVE_INFINITY);
maxAcc.accumulate(1.5);
maxAcc.accumulate(3.5);
maxAcc.accumulate(2.0);
System.out.println("最大值: " + maxAcc.get()); // 3.5
}
}
VarHandle(JDK 9+)
VarHandle 是 JDK 9 引入的变量句柄 API,它提供了对字段和数组元素的灵活访问,支持多种内存排序模式。VarHandle 是原子类的底层实现机制,提供了比原子类更细粒度的控制。
VarHandle vs 原子类
| 特性 | 原子类 | VarHandle |
|---|---|---|
| 内存排序控制 | 固定(volatile 语义) | 可选择(plain/opaque/acquire/release/volatile) |
| 适用范围 | 包装整个变量 | 直接操作字段、数组元素 |
| 性能 | 较好 | 更好(减少间接访问) |
| 使用难度 | 简单 | 较复杂 |
创建 VarHandle
VarHandle 通过 MethodHandles 创建:
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class VarHandleDemo {
// 需要原子操作的字段
public int counter = 0;
public volatile boolean flag = false;
// 创建 VarHandle(通常声明为 static final)
private static final VarHandle COUNTER_HANDLE;
private static final VarHandle FLAG_HANDLE;
static {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
// 获取字段的 VarHandle
COUNTER_HANDLE = lookup.findVarHandle(
VarHandleDemo.class, "counter", int.class);
FLAG_HANDLE = lookup.findVarHandle(
VarHandleDemo.class, "flag", boolean.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
public static void main(String[] args) {
VarHandleDemo demo = new VarHandleDemo();
// 读取操作
int value = (int) COUNTER_HANDLE.get(demo); // 普通读
int volatileValue = (int) COUNTER_HANDLE.getVolatile(demo); // volatile 读
int acquireValue = (int) COUNTER_HANDLE.getAcquire(demo); // acquire 读
// 写入操作
COUNTER_HANDLE.set(demo, 10); // 普通写
COUNTER_HANDLE.setVolatile(demo, 20); // volatile 写
COUNTER_HANDLE.setRelease(demo, 30); // release 写
// 原子操作
boolean success = COUNTER_HANDLE.compareAndSet(demo, 30, 40);
System.out.println("CAS 结果: " + success + ", 值: " + demo.counter);
// 原子增减
int oldValue = (int) COUNTER_HANDLE.getAndAdd(demo, 5);
System.out.println("旧值: " + oldValue + ", 新值: " + demo.counter);
// 原子自增
int prev = (int) COUNTER_HANDLE.getAndIncrement(demo);
System.out.println("自增前: " + prev + ", 自增后: " + demo.counter);
}
}
内存排序模式
VarHandle 支持多种内存排序模式,提供了比 volatile 更细粒度的控制:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| Plain | 普通读写,无内存屏障 | 单线程访问或已有其他同步机制 |
| Opaque | 保证原子性,单变量顺序一致 | 性能敏感且只需单变量可见性 |
| Acquire/Release | 获取/释放语义 | 生产者-消费者模式 |
| Volatile | 完全的 volatile 语义 | 需要完全可见性保证 |
public class MemoryOrderDemo {
int data = 0;
volatile boolean ready = false;
private static final VarHandle DATA_HANDLE;
private static final VarHandle READY_HANDLE;
static {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
DATA_HANDLE = lookup.findVarHandle(MemoryOrderDemo.class, "data", int.class);
READY_HANDLE = lookup.findVarHandle(MemoryOrderDemo.class, "ready", boolean.class);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
// 生产者:使用 release 语义写入
public void publish() {
DATA_HANDLE.set(this, 42); // 普通写
READY_HANDLE.setRelease(this, true); // release 写:保证上面的写入对其他线程可见
}
// 消费者:使用 acquire 语义读取
public int consume() {
if ((boolean) READY_HANDLE.getAcquire(this)) { // acquire 读
return (int) DATA_HANDLE.get(this);
}
return -1;
}
}
数组元素的 VarHandle
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class ArrayVarHandleDemo {
private static final VarHandle INT_ARRAY_HANDLE;
static {
INT_ARRAY_HANDLE = MethodHandles.arrayElementVarHandle(int[].class);
}
public static void main(String[] args) {
int[] array = {1, 2, 3, 4, 5};
// 原子读取数组元素
int value = (int) INT_ARRAY_HANDLE.getVolatile(array, 0);
System.out.println("array[0] = " + value);
// 原子 CAS 操作
boolean success = INT_ARRAY_HANDLE.compareAndSet(array, 0, 1, 100);
System.out.println("CAS 成功: " + success + ", array[0] = " + array[0]);
// 原子增减
int oldValue = (int) INT_ARRAY_HANDLE.getAndAdd(array, 1, 10);
System.out.println("array[1] 旧值: " + oldValue + ", 新值: " + array[1]);
}
}
访问私有字段
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class PrivateFieldAccess {
private int privateField = 100;
private static final VarHandle PRIVATE_HANDLE;
static {
try {
// 使用 privateLookupIn 访问私有字段
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(
PrivateFieldAccess.class, MethodHandles.lookup());
PRIVATE_HANDLE = lookup.findVarHandle(
PrivateFieldAccess.class, "privateField", int.class);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public static void main(String[] args) {
PrivateFieldAccess obj = new PrivateFieldAccess();
// 通过 VarHandle 访问私有字段
int value = (int) PRIVATE_HANDLE.get(obj);
System.out.println("私有字段值: " + value);
// 原子更新私有字段
PRIVATE_HANDLE.compareAndSet(obj, 100, 200);
System.out.println("更新后: " + obj.privateField);
}
}
使用场景
VarHandle 适合以下场景:
- 高性能原子操作:需要比原子类更好的性能,且能接受更复杂的 API
- 细粒度内存控制:需要精确控制内存排序语义
- 库和框架开发:构建底层并发工具
- 替代 Unsafe:避免使用不安全的 sun.misc.Unsafe
注意:对于大多数应用场景,原子类已经足够。VarHandle 主要用于需要极致性能或特殊内存排序语义的场景。
原子类选择指南
| 场景 | 推荐类 |
|---|---|
| 简单计数器 | AtomicInteger / AtomicLong |
| 高并发计数 | LongAdder / DoubleAdder |
| 对象引用更新 | AtomicReference |
| 解决 ABA 问题 | AtomicStampedReference |
| 数组元素原子操作 | AtomicIntegerArray |
| 类字段原子更新 | AtomicIntegerFieldUpdater |
| 自定义累加逻辑 | LongAccumulator |
| 高性能细粒度控制 | VarHandle(JDK 9+) |
小结
原子类是 JUC 提供的无锁并发工具,基于 CAS 机制实现线程安全操作。以下是本章的核心要点:
CAS 原理:
CAS 是一种乐观锁机制,通过 CPU 的原子指令实现"比较并交换"操作。相比传统锁机制,CAS 不会阻塞线程,在低竞争场景下性能更优。但 CAS 也存在 ABA 问题、高竞争下自旋开销大、只能保证单变量原子性等局限。
原子类分类:
- 基本类型原子类:AtomicInteger、AtomicLong、AtomicBoolean,适用于简单的原子计数和标志
- 引用类型原子类:AtomicReference、AtomicStampedReference,前者用于对象引用,后者解决 ABA 问题
- 数组类型原子类:AtomicIntegerArray、AtomicLongArray,对数组元素进行原子操作
- 字段更新器:AtomicIntegerFieldUpdater,对已有类的 volatile 字段进行原子更新
- 累加器:LongAdder、LongAccumulator,高并发计数场景的最佳选择
性能考虑:
低竞争场景下,原子类性能优于锁。高竞争场景下,LongAdder 通过分散热点策略,性能远超 AtomicLong。选择合适的工具对于并发性能至关重要。
VarHandle:
JDK 9 引入的 VarHandle 提供了比原子类更细粒度的内存访问控制,支持多种内存排序模式,是原子类的底层实现机制。大多数场景下使用原子类已足够,VarHandle 主要用于需要极致性能或特殊内存语义的场景。
使用建议:
- 简单计数器使用 AtomicInteger 或 AtomicLong
- 高并发计数使用 LongAdder
- 需要版本控制使用 AtomicStampedReference
- 已有类需要原子更新字段时使用字段更新器
- 自定义累加逻辑使用 LongAccumulator
原子类是构建无锁数据结构的基础,理解其原理有助于编写正确的并发代码。