Java并发-原子类
Java并发-原子类
核心理论
1.1 原子类的概念与作用
原子类是Java并发包提供的线程安全工具类,通过CAS(Compare-And-Swap)操作保证操作的原子性,避免使用synchronized关键字带来的性能开销。原子类支持原子性的更新基本类型、引用类型和数组元素。
1.2 原子类的分类
- 基本类型原子类:AtomicInteger、AtomicLong、AtomicBoolean
- 引用类型原子类:AtomicReference、AtomicStampedReference、AtomicMarkableReference
- 数组类型原子类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
- 字段更新器原子类:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
- 累加器原子类:LongAdder、DoubleAdder(JDK 8新增,高并发下性能优于AtomicLong)
1.3 CAS操作原理
CAS操作包含三个参数:内存位置(V)、预期原值(A)和新值(B)。当且仅当V的值等于A时,才将V的值更新为B,否则不做操作。CAS是一种无锁算法,通过硬件指令保证操作的原子性。
代码实践
2.1 基本类型原子类使用示例
public class AtomicIntegerExample {
private static final AtomicInteger count = new AtomicInteger(0);
public static void increment() {
// CAS操作:如果当前值为expect,则更新为update
count.compareAndSet(0, 1);
// 原子自增
count.incrementAndGet();
// 原子自减
count.decrementAndGet();
// 原子添加指定值
count.addAndGet(5);
}
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
increment();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终结果: " + count.get()); // 输出2000,保证线程安全
}
}
2.2 解决ABA问题的AtomicStampedReference
public class AtomicStampedReferenceExample {
private static final AtomicStampedReference<Integer> value = new AtomicStampedReference<>(0, 0);
public static void main(String[] args) {
// 线程1执行ABA操作
new Thread(() -> {
int stamp = value.getStamp();
System.out.println("线程1获取当前值: " + value.getReference() + ", 版本号: " + stamp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 执行ABA操作
value.compareAndSet(0, 1, stamp, stamp + 1);
System.out.println("线程1将值从0改为1,新版本号: " + value.getStamp());
value.compareAndSet(1, 0, value.getStamp(), value.getStamp() + 1);
System.out.println("线程1将值从1改回0,新版本号: " + value.getStamp());
}).start();
// 线程2尝试更新
new Thread(() -> {
int stamp = value.getStamp();
System.out.println("线程2获取当前值: " + value.getReference() + ", 版本号: " + stamp);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 版本号已变化,更新失败
boolean success = value.compareAndSet(0, 2, stamp, stamp + 1);
System.out.println("线程2更新是否成功: " + success + ", 当前值: " + value.getReference() + ", 当前版本号: " + value.getStamp());
}).start();
}
}
2.3 高性能累加器LongAdder
public class LongAdderExample {
private static final LongAdder counter = new LongAdder();
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
counter.increment();
}
};
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(task);
threads.add(thread);
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("累加结果: " + counter.sum()); // 输出100000
}
}
设计思想
3.1 原子类的无锁设计
原子类基于无锁算法实现,通过CAS操作避免了传统锁机制的上下文切换和阻塞开销。在高并发场景下,无锁设计通常比锁机制具有更高的吞吐量。
3.2 LongAdder的分段累加设计
LongAdder在高并发下将热点数据分散到多个Cell中,每个线程更新自己的Cell,最后汇总结果,减少了CAS竞争,显著提高了并发性能。
3.3 原子类与不可变对象
结合原子类和不可变对象可以构建线程安全的数据结构。例如,使用AtomicReference存储不可变对象的引用,通过原子更新实现无锁的线程安全。
避坑指南
4.1 CAS的ABA问题
当一个值从A变为B再变回A时,CAS操作会误认为值没有变化。解决方法:使用AtomicStampedReference添加版本号,或AtomicMarkableReference添加标记位。
4.2 原子类的过度使用
原子类适用于简单的原子操作,复杂逻辑仍需使用锁机制。过度使用原子类可能导致代码可读性下降和逻辑复杂度增加。
4.3 LongAdder的内存开销
LongAdder在高并发下会创建多个Cell对象,可能增加内存开销。在低并发场景下,AtomicLong可能是更优选择。
深度思考题
- CAS操作的优缺点是什么?在什么情况下CAS性能不如锁机制?
- 除了ABA问题,CAS操作还有哪些局限性?
- LongAdder和AtomicLong的实现原理有何区别?各自适用场景是什么?
思考题回答:
CAS优点:无锁、低开销、高并发下性能好;缺点:可能导致ABA问题、循环时间长开销大、只能保证单个变量的原子操作。当竞争激烈导致CAS失败率高时,循环重试会消耗大量CPU资源,性能可能不如锁机制。
CAS的其他局限性:只能保证单个变量的原子操作、无法实现阻塞、可能导致活锁(线程反复重试CAS操作)。
AtomicLong基于单个变量的CAS操作实现;LongAdder通过分段CAS(多个Cell)实现,减少竞争。适用场景:低并发或需要精确计数用AtomicLong;高并发且允许最终一致性用LongAdder。