Java并发编程中volatile与atomic的对比及应用指南
在Java并发编程实践中,开发人员经常需要处理volatile
和atomic
这两类特殊变量类型。这两种机制虽然都与线程安全相关,但它们的实现原理和使用场景存在本质差异。我们通过一个典型场景来理解它们的区别:假设有10个线程同时执行计数器递增操作,使用普通变量时最终结果可能小于100,而使用不同机制会产生怎样的变化?
一、底层内存模型的差异
1.1 volatile的内存可见性实现
volatile
关键字通过强制读写操作直接与主内存交互,实现了变量的可见性保障。当线程A修改volatile变量时:
- 修改后的值立即写入主内存
- 其他线程的工作内存中该变量的缓存自动失效
- 后续读取必须重新从主内存加载
class VisibilityDemo {
volatile boolean flag = true;
void writer() {
flag = false; // 修改立即对其他线程可见
}
void reader() {
while(flag) {
// 实时读取最新值
}
}
}
1.2 atomic的CAS原理
Atomic类通过CAS(Compare And Swap)指令实现原子操作:
public class AtomicDemo {
private AtomicInteger counter = new AtomicInteger(0);
public void safeIncrement() {
while(true) {
int current = counter.get();
int next = current + 1;
if(counter.compareAndSet(current, next)) {
break;
}
}
}
}
CAS操作包含三个操作数:内存位置V、预期原值A、新值B。当且仅当V的值等于A时,处理器才会用B更新V的值。
二、原子性保障程度对比
2.1 volatile的原子性局限
volatile变量只能保证单次读/写操作的原子性:
volatile int count = 0;
void unsafeIncrement() {
count++; // 包含读取-修改-写入三个操作
}
上述代码在多线程环境下可能导致更新丢失,因为自增操作不是原子性的。
2.2 atomic的复合原子操作
Atomic类可以保证复杂操作的原子性:
AtomicInteger atomicCount = new AtomicInteger(0);
void safeIncrement() {
atomicCount.getAndIncrement(); // 原子性递增
}
Atomic包还提供更复杂的操作方法:
AtomicReference<BigDecimal> balance = new AtomicReference<>(BigDecimal.ZERO);
void updateBalance(BigDecimal amount) {
balance.updateAndGet(current -> current.add(amount));
}
三、指令重排序约束
3.1 volatile的内存屏障
JVM会在volatile变量操作前后插入内存屏障:
- LoadLoad屏障:禁止上边的普通读与下边的volatile读重排序
- LoadStore屏障:禁止上边的volatile读与下边的普通写重排序
- StoreStore屏障:禁止上边的volatile写与下边的普通写重排序
- StoreLoad屏障:禁止上边的volatile写与下边的volatile读/写重排序
3.2 atomic的内存语义
Atomic类的实现依赖于volatile变量:
public class AtomicInteger extends Number {
private volatile int value;
public final int get() {
return value;
}
}
因此Atomic类具有与volatile相同的内存可见性特性,同时通过CAS保证原子性。
四、性能特征对比
4.1 volatile的性能开销
在单线程环境下测试:
volatile long vCounter = 0;
long start = System.nanoTime();
for(int i=0; i<1_000_000; i++) {
vCounter++;
}
测试结果显示volatile自增比普通变量慢约5-10倍,主要因为每次操作都需要与主内存交互。
4.2 atomic的并发性能
使用JMH进行基准测试:
@BenchmarkMode(Mode.Throughput)
public class AtomicBenchmark {
private AtomicInteger atomicCounter = new AtomicInteger();
private int plainCounter = 0;
@Benchmark
public void atomicIncrement() {
atomicCounter.getAndIncrement();
}
@Benchmark
public void plainIncrement() {
plainCounter++;
}
}
在低竞争环境下,Atomic操作耗时比synchronized快约2个数量级;但在高竞争环境下,可能需要退化为自适应自旋锁。
五、典型应用场景
5.1 volatile的最佳实践
- 状态标志位控制
class TaskRunner {
volatile boolean isRunning = true;
public void stop() {
isRunning = false;
}
public void run() {
while(isRunning) {
// 执行任务
}
}
}
- 单例模式双重检查锁定
class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
5.2 atomic的典型用例
- 高性能计数器
class HitCounter {
private AtomicLong count = new AtomicLong();
public void increment() {
count.incrementAndGet();
}
public long getCount() {
return count.get();
}
}
- 累积统计
class Statistics {
private AtomicIntegerArray temperatureReadings =
new AtomicIntegerArray(24);
public void updateHourlyTemp(int hour, int temp) {
temperatureReadings.set(hour, temp);
}
}
- 状态机转换
class StateMachine {
private AtomicReference<State> currentState =
new AtomicReference<>(State.INIT);
public void transition(State newState) {
currentState.updateAndGet(old -> {
if(isValidTransition(old, newState)) {
return newState;
}
return old;
});
}
}
六、特殊场景下的注意事项
6.1 复合操作的陷阱
多个原子操作的组合不一定是原子性的:
class BankAccount {
private AtomicReference<BigDecimal> balance =
new AtomicReference<>(BigDecimal.ZERO);
// 非原子操作
void transfer(BankAccount target, BigDecimal amount) {
balance.updateAndGet(b -> b.subtract(amount));
target.balance.updateAndGet(b -> b.add(amount));
}
}
此时需要使用显式锁或事务内存来保证整体原子性。
6.2 ABA问题的解决方案
使用AtomicStampedReference解决CAS的ABA问题:
AtomicStampedReference<Integer> atomicRef =
new AtomicStampedReference<>(0, 0);
void updateWithStamp() {
int[] stampHolder = new int[1];
int currentStamp = atomicRef.getStamp();
int currentValue = atomicRef.get(stampHolder);
if(atomicRef.compareAndSet(currentValue, 42, currentStamp, currentStamp+1)) {
// 更新成功
}
}
七、JVM层面的优化策略
7.1 缓存行填充优化
防止伪共享的优化技巧:
@Contended
class VolatileHolder {
volatile long value1;
volatile long value2;
}
Java 8引入@Contended注解自动进行缓存行填充。
7.2 偏向锁与CAS
JVM对Atomic类的优化:
- 通过-XX:+UseBiasedLocking启用偏向锁
- 当检测到不存在锁竞争时,自动优化锁机制
- 对Atomic类的方法调用进行内联优化
八、混合使用模式
结合volatile和Atomic类的典型模式:
class HybridCounter {
private volatile int version;
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
version++;
}
public int getCount() {
return count.get();
}
public int getVersion() {
return version;
}
}
这种设计将高频更新的计数器与低频更新的版本号分离,兼顾性能与可见性需求。