1. Java原子类概述
在多线程编程中,原子操作是最小执行单位,不会被线程调度机制打断。Java从JDK 1.5开始提供了java.util.concurrent.atomic包,其中包含了一系列原子类,用于在多线程环境下进行无锁的线程安全操作。
原子类的核心思想是利用CAS(Compare And Swap)算法实现非阻塞同步,相比传统的synchronized关键字,它在大多数场景下能提供更好的性能表现。CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。当且仅当V的值等于A时,处理器才会用B更新V的值,否则不执行更新。
重要提示:虽然原子类性能优异,但它并不适用于所有场景。当竞争非常激烈时,CAS操作可能会频繁失败并重试,反而会降低性能。
2. 原子类核心原理
2.1 CAS实现机制
Java原子类的实现依赖于Unsafe类提供的底层CAS操作。以AtomicInteger为例,其核心实现代码片段如下:
java复制private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
这段代码展示了几个关键点:
- 通过Unsafe类获取字段的内存偏移量
- 使用volatile保证value的可见性
- compareAndSet方法底层调用Unsafe的CAS操作
2.2 内存屏障与可见性
原子类通过volatile变量和内存屏障保证内存可见性。在x86架构上,Java内存模型中的volatile写-读操作会插入以下屏障:
- StoreStore屏障:禁止上面的普通写和下面的volatile写重排序
- StoreLoad屏障:禁止上面的volatile写和下面可能的volatile读/写重排序
3. 常用原子类详解
3.1 基本类型原子类
3.1.1 AtomicInteger
AtomicInteger提供原子性的int值操作,常用方法包括:
- get()/set():获取/设置当前值
- getAndIncrement():先获取当前值再自增
- incrementAndGet():先自增再获取结果
- compareAndSet():CAS操作
java复制AtomicInteger counter = new AtomicInteger(0);
// 线程安全的自增
counter.incrementAndGet();
// 原子性更新
int oldValue, newValue;
do {
oldValue = counter.get();
newValue = calculateNewValue(oldValue);
} while (!counter.compareAndSet(oldValue, newValue));
3.1.2 AtomicLong
与AtomicInteger类似,但针对long类型。需要注意的是,在32位JVM上,long的读写可能不是原子性的,但AtomicLong通过特殊处理保证了原子性。
3.1.3 AtomicBoolean
最简单的原子类,内部使用int值0和1表示false和true。
3.2 引用类型原子类
3.2.1 AtomicReference
可以对对象引用进行原子操作,典型应用场景包括非阻塞栈的实现:
java复制public class ConcurrentStack<E> {
private AtomicReference<Node<E>> top = new AtomicReference<>();
public void push(E item) {
Node<E> newHead = new Node<>(item);
Node<E> oldHead;
do {
oldHead = top.get();
newHead.next = oldHead;
} while (!top.compareAndSet(oldHead, newHead));
}
public E pop() {
Node<E> oldHead;
Node<E> newHead;
do {
oldHead = top.get();
if (oldHead == null) return null;
newHead = oldHead.next;
} while (!top.compareAndSet(oldHead, newHead));
return oldHead.item;
}
private static class Node<E> {
final E item;
Node<E> next;
Node(E item) {
this.item = item;
}
}
}
3.2.2 AtomicStampedReference
解决ABA问题的引用原子类,通过添加版本戳(stamp)来检测值是否被修改过。
3.2.3 AtomicMarkableReference
与AtomicStampedReference类似,但使用boolean标记代替版本号,适用于只需要知道是否被修改过的场景。
3.3 数组原子类
3.3.1 AtomicIntegerArray
原子性地更新int数组中的元素,需要注意的是它并不会保证对数组引用的原子更新,只保证数组元素的原子更新。
java复制AtomicIntegerArray array = new AtomicIntegerArray(10);
array.set(0, 100); // 原子性设置
array.compareAndSet(0, 100, 200); // 原子性CAS
3.3.2 AtomicLongArray/AtomicReferenceArray
分别对应long数组和对象引用数组的原子操作。
3.4 字段更新器
3.4.1 AtomicIntegerFieldUpdater
允许对指定类的volatile int字段进行原子更新,适用于需要原子性但不想创建AtomicInteger实例的场景。
java复制class Counter {
private volatile int count;
private static final AtomicIntegerFieldUpdater<Counter> updater =
AtomicIntegerFieldUpdater.newUpdater(Counter.class, "count");
public int increment() {
return updater.incrementAndGet(this);
}
}
3.4.2 AtomicLongFieldUpdater/AtomicReferenceFieldUpdater
类似地,分别针对long和引用类型的字段更新器。
4. 原子类高级特性
4.1 累积器与累加器
JDK 8引入了LongAdder和DoubleAdder,在高并发场景下提供更好的性能。它们通过分散热点数据来减少竞争:
java复制LongAdder adder = new LongAdder();
// 多线程调用
adder.add(1);
// 获取总和
long sum = adder.sum();
原理分析:
- 内部维护一个Cell数组和一个base值
- 无竞争时只更新base
- 有竞争时初始化Cell数组,线程hash到不同Cell上更新
- 最终结果为base + ∑Cell[i]
4.2 原子类性能优化
-
伪共享(false sharing)问题:多个原子变量位于同一缓存行,导致不必要的缓存同步。解决方案:
- JDK 8使用@Contended注解自动填充
- 手动填充:在关键字段前后添加long类型的无用字段
-
退避策略:当CAS失败时,适当的退避可以减少竞争。常见策略:
- Thread.yield()
- Thread.sleep(少量时间)
- 指数退避
5. 原子类实战应用
5.1 计数器实现
java复制public class CompactCounter {
private final AtomicInteger counter = new AtomicInteger(0);
private static final int MASK = 0x00FFFFFF;
public int increment() {
int current;
int next;
do {
current = counter.get();
next = (current + 1) & MASK;
if (next == 0) {
// 处理溢出
next = 1;
}
} while (!counter.compareAndSet(current, next));
return next;
}
}
5.2 无锁队列实现
java复制public class LockFreeQueue<E> {
private static class Node<E> {
final E item;
volatile Node<E> next;
Node(E item) {
this.item = item;
}
}
private final AtomicReference<Node<E>> head;
private final AtomicReference<Node<E>> tail;
public LockFreeQueue() {
Node<E> dummy = new Node<>(null);
head = new AtomicReference<>(dummy);
tail = new AtomicReference<>(dummy);
}
public boolean put(E item) {
Node<E> newNode = new Node<>(item);
while (true) {
Node<E> currentTail = tail.get();
Node<E> tailNext = currentTail.next;
if (currentTail == tail.get()) {
if (tailNext != null) {
// 有其他线程已经添加了节点但还没更新tail
tail.compareAndSet(currentTail, tailNext);
} else {
if (currentTail.next.compareAndSet(null, newNode)) {
tail.compareAndSet(currentTail, newNode);
return true;
}
}
}
}
}
}
5.3 性能敏感场景的优化
在高性能计算中,可以考虑以下优化策略:
- 使用Unsafe直接操作内存(不推荐,破坏可移植性)
- 针对特定CPU架构优化(如x86的pause指令)
- 使用平台相关的原子指令(如ARM的LDREX/STREX)
6. 常见问题与解决方案
6.1 ABA问题
问题描述:线程1读取值为A,线程2将值改为B又改回A,线程1的CAS操作仍然会成功。
解决方案:
- 使用AtomicStampedReference
- 使用版本号或时间戳
- 对于简单场景,可以忽略ABA问题(如只关心最终结果)
6.2 性能瓶颈
问题表现:高竞争下CAS失败率高,CPU占用高但吞吐量低。
优化方案:
- 使用LongAdder代替AtomicLong
- 减小临界区范围
- 采用分层设计,先线程本地计算再全局汇总
6.3 内存消耗
问题表现:大量使用原子类导致内存占用高。
优化建议:
- 对于非热点数据,考虑使用普通变量加锁
- 使用字段更新器代替独立原子类实例
- 评估是否真的需要原子性保证
7. 原子类最佳实践
-
适用场景选择:
- 适合读多写少、竞争不激烈的场景
- 不适合写多读少、竞争激烈的场景
-
API使用建议:
- 优先使用getAndUpdate/updateAndGet等组合操作
- 避免在循环中频繁调用get(),可能影响性能
-
测试验证:
- 使用JMH进行微基准测试
- 多线程环境下充分测试边界条件
-
监控与调优:
- 监控CAS失败率
- 根据实际性能调整策略
原子类为Java并发编程提供了强大的工具,但需要根据具体场景合理选择和使用。理解其底层原理有助于更好地发挥其性能优势,避免潜在问题。
