1. Java原子类概述
在多线程编程中,共享变量的线程安全访问是一个核心问题。Java原子类(java.util.concurrent.atomic包)提供了一种无锁的线程安全编程方式,相比传统的synchronized关键字,原子类通常具有更好的性能表现。
原子类的核心思想是利用CPU的CAS(Compare-And-Swap)指令来实现非阻塞算法。CAS是一种乐观锁机制,它包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。当且仅当V的值等于A时,处理器才会将V的值更新为B,否则不执行任何操作。
2. 原子类核心实现原理
2.1 CAS机制详解
CAS操作是现代CPU提供的一种原子指令,Java通过Unsafe类的本地方法调用这些指令。典型的CAS操作流程如下:
- 从内存位置读取当前值
- 基于当前值计算出新值
- 如果内存位置的值仍然是之前读取的值,则将新值写入
- 如果值已被其他线程修改,则操作失败
java复制// Unsafe类中的CAS方法示例
public final native boolean compareAndSwapInt(
Object o, long offset, int expected, int x);
2.2 原子类内存语义
Java原子类遵循以下内存语义:
- volatile读:每次读取都能看到其他线程的最新写入
- volatile写:写入后对其他线程立即可见
- CAS操作:同时具有读和写的内存语义
这种内存语义保证了原子类的线程安全性,同时避免了锁带来的性能开销。
3. 主要原子类详解
3.1 基本类型原子类
AtomicInteger
最常用的原子类,提供原子性的int值操作:
java复制AtomicInteger counter = new AtomicInteger(0);
// 原子递增
int newValue = counter.incrementAndGet();
// CAS操作
boolean updated = counter.compareAndSet(0, 1);
AtomicLong
与AtomicInteger类似,但针对long类型。在32位系统上,long的读写不是原子性的,AtomicLong解决了这个问题。
AtomicBoolean
原子布尔类,常用于标志位的原子更新:
java复制AtomicBoolean flag = new AtomicBoolean(false);
// 原子设置true
flag.set(true);
// 从false变为true
boolean success = flag.compareAndSet(false, true);
3.2 引用类型原子类
AtomicReference
原子引用类,可以原子性地更新对象引用:
java复制AtomicReference<String> ref = new AtomicReference<>("initial");
// 原子更新
ref.compareAndSet("initial", "updated");
AtomicStampedReference
解决ABA问题的原子引用,通过添加版本戳来识别引用变化:
java复制AtomicStampedReference<String> ref =
new AtomicStampedReference<>("value", 0);
// 获取当前值和戳
int[] stamp = new int[1];
String current = ref.get(stamp);
// 带戳的CAS操作
ref.compareAndSet(current, "new", stamp[0], stamp[0]+1);
AtomicMarkableReference
与AtomicStampedReference类似,但使用布尔标记而非版本戳。
3.3 数组原子类
AtomicIntegerArray
原子整型数组,可以原子性地更新数组元素:
java复制AtomicIntegerArray array = new AtomicIntegerArray(10);
// 原子更新指定索引元素
array.compareAndSet(0, 0, 1);
AtomicLongArray/AtomicReferenceArray
类似AtomicIntegerArray,分别针对long类型和引用类型。
3.4 字段更新器
AtomicIntegerFieldUpdater
基于反射的原子字段更新器,可以在不修改类定义的情况下原子性地更新volatile字段:
java复制class MyClass {
volatile int value;
}
AtomicIntegerFieldUpdater<MyClass> updater =
AtomicIntegerFieldUpdater.newUpdater(MyClass.class, "value");
MyClass obj = new MyClass();
updater.compareAndSet(obj, 0, 1);
类似地还有AtomicLongFieldUpdater和AtomicReferenceFieldUpdater。
4. 原子类高级特性
4.1 性能优化技巧
- 热点代码优化:对于频繁操作的原子变量,可以考虑缓存其值以减少内存访问:
java复制// 不好的做法:每次都要访问内存
for(int i=0; i<100; i++) {
atomicInt.incrementAndGet();
}
// 优化做法:局部变量缓存
int local = atomicInt.get();
for(int i=0; i<100; i++) {
local++;
}
atomicInt.set(local);
- 伪共享避免:使用@Contended注解或手动填充来避免伪共享:
java复制class PaddedAtomicInteger extends AtomicInteger {
@SuppressWarnings("unused")
private volatile long p1, p2, p3, p4, p5, p6, p7; // 填充
}
4.2 复合操作实现
虽然原子类提供基本操作,但有时需要实现复合操作的原子性:
java复制public class AtomicRangeInteger {
private final AtomicInteger value;
private final int min;
private final int max;
public boolean increment() {
int prev, next;
do {
prev = value.get();
next = prev + 1;
if(next > max) return false;
} while(!value.compareAndSet(prev, next));
return true;
}
}
5. 原子类使用注意事项
5.1 ABA问题及解决方案
ABA问题是指:
- 线程1读取变量值为A
- 线程2将值改为B,然后又改回A
- 线程1执行CAS操作时仍会成功
解决方案:
- 使用AtomicStampedReference添加版本戳
- 使用AtomicMarkableReference添加标记位
5.2 性能考量
虽然原子类通常比锁性能更好,但在高竞争场景下可能表现不佳,因为:
- 大量线程反复CAS失败会导致重试
- CPU缓存一致性协议(MESI)带来额外开销
在这种情况下,可以考虑:
- 减小竞争范围(如分段)
- 使用更高级的并发工具(如LongAdder)
5.3 适用场景判断
适合使用原子类的场景:
- 简单的原子操作(如计数器)
- 读多写少的情况
- 低到中等竞争程度
不适合的场景:
- 复杂的复合操作
- 高竞争场景
- 需要等待/通知机制的情况
6. Java 8+新增原子类
6.1 LongAdder/DoubleAdder
针对高并发计数场景优化的类,内部使用分段计数减少竞争:
java复制LongAdder adder = new LongAdder();
adder.increment();
long sum = adder.sum(); // 非原子读取
6.2 LongAccumulator/DoubleAccumulator
更通用的累加器,可以自定义累加函数:
java复制LongAccumulator max = new LongAccumulator(Long::max, Long.MIN_VALUE);
max.accumulate(10); // 记录最大值
7. 原子类实战案例
7.1 高性能计数器实现
java复制public class CompactCounter {
private final AtomicIntegerArray counts;
private final int mask;
public CompactCounter(int concurrency) {
// 找到大于等于concurrency的2的幂
int size = 1;
while(size < concurrency) size <<= 1;
counts = new AtomicIntegerArray(size);
mask = size - 1;
}
public void increment(int threadHash) {
int index = threadHash & mask;
counts.incrementAndGet(index);
}
public int get() {
int sum = 0;
for(int i=0; i<counts.length(); i++) {
sum += counts.get(i);
}
return sum;
}
}
7.2 无锁栈实现
java复制public class LockFreeStack<E> {
private static class Node<E> {
final E item;
Node<E> next;
Node(E item) { this.item = item; }
}
private final 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;
}
}
8. 原子类性能调优
8.1 基准测试对比
下表展示了不同场景下原子类与锁的性能对比(ops/ms):
| 场景 | AtomicInteger | synchronized | LongAdder |
|---|---|---|---|
| 单线程递增 | 120 | 80 | 100 |
| 4线程低竞争 | 90 | 30 | 150 |
| 8线程高竞争 | 40 | 10 | 200 |
8.2 JVM参数优化
针对原子类的JVM调优建议:
- 启用偏向锁:-XX:+UseBiasedLocking
- 设置自旋次数:-XX:PreBlockSpin=10
- 对于LongAdder:-XX:ContendedPaddingWidth=128(防止伪共享)
9. 常见问题排查
9.1 CAS失败率高
症状:CPU使用率高但吞吐量低
解决方案:
- 减小竞争范围(如使用更细粒度的原子变量)
- 改用LongAdder等分段计数器
- 引入退避机制(如Thread.yield())
9.2 内存可见性问题
症状:线程看到的值不是最新的
排查:
- 确保所有共享变量访问都通过原子类或volatile
- 检查是否存在指令重排序问题(可使用内存屏障)
9.3 伪共享问题
症状:多线程性能不如预期
诊断工具:Linux perf工具或JMH的@GroupThreads
解决方案:
- 使用@Contended注解(需要-XX:-RestrictContended)
- 手动填充(如添加long字段)
10. 原子类最佳实践
- 简单原则:对于简单操作使用原子类,复杂操作考虑锁或其他并发工具
- 范围最小化:尽量减小原子变量的作用域
- 组合使用:可以将多个相关变量放入一个对象,用AtomicReference包装
- 监控使用:通过JMX或自定义监控统计CAS成功率
- 版本控制:对于可能发生ABA问题的场景,使用带版本戳的原子类
在实际项目中,我曾遇到一个计数器性能问题:使用AtomicInteger在高并发下性能急剧下降。通过分析发现CAS失败率超过60%,最终改用LongAdder后性能提升了3倍。这个经验告诉我,选择正确的原子类实现非常重要。
