在 Java 中,原子性(Atomicity) 是指一个操作不可被中断,要么全部执行成功,要么完全不执行,不会出现中间状态。原子性是线程安全的核心要求之一,尤其在多线程环境下,非原子操作可能导致数据不一致或竞态条件(Race Condition)。
1. 原子性问题的根源
非原子操作由多个步骤组成,在多线程环境下可能被其他线程打断。例如:
int count = 0;
// 以下操作是非原子的:
count++; // 等价于 count = count + 1实际执行步骤:
- 1. 读取 count 的当前值(read)
- 2. 将值加 1(modify)
- 3. 写入新值(write)
若两个线程同时执行 count++,可能发生以下情况:
Thread1: 读取 count=0 → 修改为 1 → 写入 count=1
Thread2: 读取 count=0 → 修改为 1 → 写入 count=1
最终结果:count=1(预期是 2)2. Java 中的原子操作
2.1 基本数据类型的原子性
- o 32 位 JVM:
- o int、boolean、float 等 32 位变量的读写是原子的。
- o long、double 等 64 位变量的读写是非原子的(需要分两次 32 位操作)。
- o 64 位 JVM:
- o 所有基本类型的读写都是原子的。
- o 注意:即使读写是原子的,复合操作(如 i++)仍是非原子的!
2.2 volatile 关键字
- o 保证可见性:修改后的值对其他线程立即可见。
- o 禁止指令重排序:优化时不会打乱代码执行顺序。
- o 不保证原子性:例如 volatile int count 的 count++ 仍然是非原子操作。
3. 如何实现原子操作
方法 1:使用synchronized同步
通过同步代码块或方法,确保操作原子性:
public class Counter {
private int count = 0;
public synchronized void increment() { // 同步方法
count++;
}
public int getCount() {
return count;
}
}方法 2:使用Lock显式锁
通过 ReentrantLock 显式控制锁:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}方法 3:使用原子类(Atomic Classes)
Java 提供
java.util.concurrent.atomic 包下的原子类,基于 CAS(Compare-And-Swap)实现无锁原子操作:
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子自增
}
public int getCount() {
return count.get();
}
}4. CAS(Compare-And-Swap)
CAS 是一种无锁算法,通过硬件指令(如 x86 的 CMPXCHG)实现原子操作。其核心逻辑如下:
public class AtomicInteger {
private volatile int value;
public int incrementAndGet() {
int oldValue;
int newValue;
do {
oldValue = value;
newValue = oldValue + 1;
} while (!compareAndSet(oldValue, newValue)); // CAS 循环
return newValue;
}
public final boolean compareAndSet(int expect, int update) {
// 使用 Unsafe 类调用硬件指令
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}- o 优点:无锁,性能高。
- o 缺点:
- o ABA 问题:值从 A → B → A,CAS 无法感知中间变化。可通过 AtomicStampedReference 解决。
- o 自旋时间长时 CPU 开销大。
5. Java 中的原子类
原子类说明AtomicInteger原子操作的 int 类型AtomicLong原子操作的 long 类型AtomicBoolean原子操作的 boolean 类型AtomicReference<V>原子操作的引用类型AtomicStampedReference解决 CAS 的 ABA 问题LongAdder高并发下性能优于 AtomicLong
6. 示例:原子类 vs 非原子类
非原子操作导致问题
public class NonAtomicDemo {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
count++;
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + count); // 结果可能小于 20000
}
}使用原子类解决
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicDemo {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
count.incrementAndGet();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + count.get()); // 输出 20000
}
}7. 最佳实践
- 1. 优先使用原子类:适用于简单原子操作(如计数器)。
- 2. 复合操作仍需同步:即使单个操作是原子的,组合操作仍需锁或 synchronized。
- 3. 避免过度使用 CAS:自旋可能浪费 CPU 资源。
- 4. 理解硬件支持:CAS 依赖 CPU 指令,不同平台性能可能不同。
掌握原子性是实现高效线程安全代码的关键。合理选择 synchronized、Lock 或原子类,可以平衡性能与安全性。
