1. 原子操作类的前世今生
第一次接触Java并发编程时,我对着i++这样的简单操作发愣——在单线程环境下它完美工作,但在多线程场景竟会出错。这个看似简单的自增操作,在JVM底层实际分为"读取-修改-写入"三个步骤,线程切换可能导致更新丢失。正是这类问题催生了java.util.concurrent.atomic包的诞生。
2004年JSR 166将atomic包引入Java 5,它提供了一种无锁(lock-free)的线程安全编程方式。与synchronized这种悲观锁不同,atomic类采用CAS(Compare-And-Swap)这种乐观锁机制,在多数没有竞争的场景下性能远超传统锁。如今在计数器、序列号生成、状态标志等场景,atomic类已成为Java并发编程的基础构件。
2. 核心原理深度剖析
2.1 CAS的魔法机制
CAS操作包含三个参数:内存位置V、预期原值A和新值B。当且仅当V的值等于A时,处理器才会用B更新V的值,否则不执行任何操作。整个操作是原子性的,由CPU指令直接支持。在x86架构上对应的是cmpxchg指令,ARM架构则是ldrex/strex指令对。
java复制// AtomicInteger中的CAS实现
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
注意:虽然CAS是原子操作,但存在ABA问题——如果一个值从A变成B又变回A,CAS会误认为没有变化。解决方案是使用版本号或时间戳,如AtomicStampedReference。
2.2 硬件层面的内存屏障
现代CPU为了性能会进行乱序执行,atomic类通过内存屏障(Memory Barrier)保证可见性。在x86上,volatile写对应lock前缀指令,会清空写缓冲区,确保修改立即对其他CPU可见。这也是为什么atomic类的操作具有happens-before语义。
2.3 伪共享与缓存行填充
多个atomic变量若位于同一缓存行(通常64字节),一个CPU核心的修改会导致其他核心的缓存行失效,这种伪共享(False Sharing)会严重降低性能。解决方案是使用@sun.misc.Contended注解进行缓存行填充:
java复制// JDK内部的缓存行填充实现
public class AtomicLong {
@sun.misc.Contended
private volatile long value;
}
3. 关键原子类实战指南
3.1 计数器之王:AtomicLong
统计接口调用次数时,我曾对比过三种实现:
- synchronized方式:QPS约15万
- LongAdder:QPS约1200万
- AtomicLong:QPS约400万
java复制// 性能测试代码片段
void test() {
long start = System.currentTimeMillis();
IntStream.range(0, THREADS).forEach(i -> {
new Thread(() -> {
for (int j = 0; j < CYCLES; j++) {
counter.increment(); // 替换不同实现
}
}).start();
});
//...打印QPS结果
}
实战经验:低竞争时用AtomicLong,高并发统计用LongAdder。后者采用分段计数思想,适合统计场景但读取结果不够实时。
3.2 状态标志优选:AtomicBoolean
实现服务开关功能时,相比volatile boolean + synchronized,AtomicBoolean是更优雅的选择:
java复制private final AtomicBoolean running = new AtomicBoolean(true);
void shutdown() {
running.set(false); // 无需synchronized
}
void doWork() {
while (running.get()) {
// 业务逻辑
}
}
3.3 复杂对象更新:AtomicReference
在实现无锁栈时,AtomicReference展现了其威力:
java复制public class ConcurrentStack<E> {
private static class Node<E> {
final E item;
Node<E> next;
// 构造方法...
}
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));
}
}
4. 高阶应用与性能调优
4.1 计数器家族的演进
从AtomicLong到LongAdder再到LongAccumulator,JDK的计数器实现不断进化:
| 实现类 | 适用场景 | 特点 |
|---|---|---|
| AtomicLong | 低竞争环境 | 精确计数,API丰富 |
| LongAdder | 高并发统计 | 分段计数,最终一致性 |
| LongAccumulator | 自定义运算规则 | 支持lambda表达式定义运算 |
4.2 字段更新器的妙用
当需要原子更新某个对象的字段时,不必将整个对象包装成atomic类:
java复制class User {
volatile int score;
}
// 初始化字段更新器
private static final AtomicIntegerFieldUpdater<User> updater =
AtomicIntegerFieldUpdater.newUpdater(User.class, "score");
// 使用方式
User user = new User();
updater.incrementAndGet(user); // 原子增加score
注意事项:字段必须是volatile修饰,且不能是private的(受限于反射API)
4.3 避免过度使用atomic
虽然atomic类性能优异,但以下情况应考虑替代方案:
- 复合操作:多个atomic变量需要保持一致性时,考虑锁或事务
- 频繁读少量写:CopyOnWriteArrayList可能更合适
- 超高并发写入:考虑LongAdder等专门优化类
5. 生产环境问题排查实录
5.1 CAS的螺旋上升问题
在实现分布式ID生成器时,我曾遇到CAS重试导致的CPU飙升:
java复制public long nextId() {
long current;
long next;
do {
current = counter.get();
next = current + 1;
if (next >= MAX_ID) {
next = 0; // 循环使用
}
} while (!counter.compareAndSet(current, next)); // 竞争激烈时死循环
return current;
}
解决方案是引入退避机制:
java复制int retries = 0;
do {
current = counter.get();
next = current + 1;
if (!counter.compareAndSet(current, next)) {
if (retries++ > MAX_RETRIES) {
throw new IllegalStateException("竞争过于激烈");
}
Thread.yield(); // 或Thread.sleep(1)
}
} while (false);
5.2 内存泄漏陷阱
使用AtomicReference实现对象池时,未正确清理可能导致内存泄漏:
java复制// 错误实现
public class ObjectPool<T> {
private final AtomicReference<LinkedList<T>> pool = new AtomicReference<>();
public T borrow() {
LinkedList<T> current, newList;
do {
current = pool.get();
if (current == null || current.isEmpty()) {
return createNewObject();
}
newList = new LinkedList<>(current);
T obj = newList.removeFirst();
} while (!pool.compareAndSet(current, newList));
return obj;
}
}
问题在于每次borrow都创建新LinkedList。正确做法应重用集合对象,或使用更高效的并发集合。
5.3 数值溢出防护
AtomicInteger的incrementAndGet()在达到Integer.MAX_VALUE时会静默回绕。金融系统需要额外检查:
java复制public int safeIncrement(AtomicInteger counter) {
int prev, next;
do {
prev = counter.get();
if (prev == Integer.MAX_VALUE) {
throw new IllegalStateException("计数器溢出");
}
next = prev + 1;
} while (!counter.compareAndSet(prev, next));
return next;
}
6. 现代Java中的增强特性
6.1 VarHandle带来的革新
Java 9引入的VarHandle提供了更灵活的原子访问方式:
java复制private static final VarHandle COUNT_HANDLE;
static {
try {
COUNT_HANDLE = MethodHandles.lookup()
.findVarHandle(Counter.class, "count", int.class);
} catch (Exception e) {
throw new Error(e);
}
}
class Counter {
private volatile int count;
void increment() {
COUNT_HANDLE.getAndAdd(this, 1);
}
}
相比Atomic类,VarHandle的优势在于:
- 直接操作对象字段,无需包装类
- 支持更多原子操作类型
- 与JVM内存模型深度集成
6.2 响应式编程中的原子类
在Reactor或RxJava中,原子类常用于状态管理:
java复制AtomicInteger pendingRequests = new AtomicInteger();
Flux.range(1, 100)
.doOnRequest(n -> pendingRequests.addAndGet((int)n))
.doOnNext(i -> processItem(i))
.doOnComplete(() -> log.info("Total requests: {}", pendingRequests.get()))
.subscribe();
这种场景下要特别注意线程可见性问题,确保所有操作都在正确的线程调度器上执行。