⚛️ 原子类
掌握Java原子类的使用、CAS算法原理以及ABA问题的解决方案
学习目标
- 了解Atomic包的设计思想
- 掌握基本原子类的使用
- 理解AtomicReference的应用
- 学习AtomicStampedReference解决ABA问题
- 深入理解CAS算法原理
Atomic包概述
Java的java.util.concurrent.atomic包提供了一系列原子类,这些类可以在多线程环境下进行原子操作,无需使用synchronized关键字或其他锁机制。原子类基于CAS(Compare-And-Swap)算法实现,提供了高效的无锁编程解决方案。
原子类的核心优势在于提供了无锁的线程安全操作,避免了传统锁机制可能带来的性能开销和死锁风险。
原子类的分类
AtomicInteger详解
AtomicInteger是最常用的原子类之一,提供了对int类型变量的原子操作。它内部使用volatile关键字保证可见性,使用CAS算法保证原子性。
基本使用示例
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerDemo {
private static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
// 创建多个线程进行计数
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.incrementAndGet(); // 原子递增
}
});
threads[i].start();
}
// 等待所有线程完成
for (Thread thread : threads) {
thread.join();
}
System.out.println("最终计数值: " + counter.get()); // 输出: 10000
}
}
常用方法
- get():获取当前值
- set(int newValue):设置新值
- getAndSet(int newValue):设置新值并返回旧值
- compareAndSet(int expect, int update):CAS操作
- incrementAndGet():先递增再获取
- getAndIncrement():先获取再递增
- addAndGet(int delta):先加法再获取
- getAndAdd(int delta):先获取再加法
AtomicReference详解
AtomicReference提供了对引用类型的原子操作,可以原子地更新对象引用。这在实现无锁数据结构时非常有用。
使用示例
import java.util.concurrent.atomic.AtomicReference;
class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
public class AtomicReferenceDemo {
private static AtomicReference userRef =
new AtomicReference<>(new User("张三", 25));
public static void main(String[] args) {
User oldUser = userRef.get();
User newUser = new User("李四", 30);
// 原子地更新用户引用
boolean success = userRef.compareAndSet(oldUser, newUser);
if (success) {
System.out.println("更新成功: " + userRef.get());
} else {
System.out.println("更新失败");
}
}
}
CAS算法原理
CAS(Compare-And-Swap)是一种无锁算法,它包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。当且仅当V的值等于A时,CAS才会将V的值设为B,否则不会执行任何操作。
1. 比较内存位置V的值与预期值A
2. 如果相等,将内存位置V的值更新为新值B
3. 如果不相等,不做任何操作
4. 返回操作是否成功
CAS的优缺点
- 无锁操作,避免线程阻塞
- 性能高,减少上下文切换
- 避免死锁问题
- ABA问题
- 循环时间长开销大
- 只能保证一个共享变量的原子操作
ABA问题及解决方案
ABA问题是CAS算法的一个经典问题:如果一个值原来是A,变成了B,又变回了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
ABA问题示例
import java.util.concurrent.atomic.AtomicReference;
public class ABADemo {
private static AtomicReference atomicRef =
new AtomicReference<>(100);
public static void main(String[] args) throws InterruptedException {
// 线程1:模拟ABA操作
Thread t1 = new Thread(() -> {
int value = atomicRef.get();
System.out.println("线程1读取值: " + value);
// 模拟一些处理时间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 尝试CAS操作
boolean success = atomicRef.compareAndSet(value, value + 1);
System.out.println("线程1 CAS操作: " + success);
});
// 线程2:制造ABA场景
Thread t2 = new Thread(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// A -> B
atomicRef.compareAndSet(100, 200);
System.out.println("线程2将值改为200");
// B -> A
atomicRef.compareAndSet(200, 100);
System.out.println("线程2将值改回100");
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终值: " + atomicRef.get());
}
}
使用AtomicStampedReference解决ABA问题
AtomicStampedReference通过引入版本号(时间戳)来解决ABA问题。每次更新时不仅比较值,还比较版本号。
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceDemo {
private static AtomicStampedReference stampedRef =
new AtomicStampedReference<>(100, 1);
public static void main(String[] args) throws InterruptedException {
// 线程1
Thread t1 = new Thread(() -> {
int[] stamp = new int[1];
int value = stampedRef.get(stamp);
int currentStamp = stamp[0];
System.out.println("线程1读取值: " + value + ", 版本号: " + currentStamp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 尝试CAS操作,版本号也要匹配
boolean success = stampedRef.compareAndSet(
value, value + 1, currentStamp, currentStamp + 1);
System.out.println("线程1 CAS操作: " + success);
});
// 线程2:制造ABA场景
Thread t2 = new Thread(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
int[] stamp = new int[1];
int value = stampedRef.get(stamp);
// A -> B
stampedRef.compareAndSet(value, 200, stamp[0], stamp[0] + 1);
System.out.println("线程2将值改为200,版本号: " + (stamp[0] + 1));
// 获取最新状态
value = stampedRef.get(stamp);
// B -> A
stampedRef.compareAndSet(value, 100, stamp[0], stamp[0] + 1);
System.out.println("线程2将值改回100,版本号: " + (stamp[0] + 1));
});
t1.start();
t2.start();
t1.join();
t2.join();
int[] finalStamp = new int[1];
int finalValue = stampedRef.get(finalStamp);
System.out.println("最终值: " + finalValue + ", 版本号: " + finalStamp[0]);
}
}
实战案例:无锁计数器
下面我们实现一个高性能的无锁计数器,对比synchronized和原子类的性能差异。
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.CountDownLatch;
public class CounterComparison {
private static final int THREAD_COUNT = 10;
private static final int INCREMENT_COUNT = 100000;
// 使用synchronized的计数器
static class SynchronizedCounter {
private long count = 0;
public synchronized void increment() {
count++;
}
public synchronized long getCount() {
return count;
}
}
// 使用AtomicLong的计数器
static class AtomicCounter {
private AtomicLong count = new AtomicLong(0);
public void increment() {
count.incrementAndGet();
}
public long getCount() {
return count.get();
}
}
public static void main(String[] args) throws InterruptedException {
// 测试synchronized计数器
testSynchronizedCounter();
// 测试原子计数器
testAtomicCounter();
}
private static void testSynchronizedCounter() throws InterruptedException {
SynchronizedCounter counter = new SynchronizedCounter();
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
long startTime = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
for (int j = 0; j < INCREMENT_COUNT; j++) {
counter.increment();
}
latch.countDown();
}).start();
}
latch.await();
long endTime = System.currentTimeMillis();
System.out.println("Synchronized计数器:");
System.out.println("最终计数: " + counter.getCount());
System.out.println("耗时: " + (endTime - startTime) + "ms\n");
}
private static void testAtomicCounter() throws InterruptedException {
AtomicCounter counter = new AtomicCounter();
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
long startTime = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
for (int j = 0; j < INCREMENT_COUNT; j++) {
counter.increment();
}
latch.countDown();
}).start();
}
latch.await();
long endTime = System.currentTimeMillis();
System.out.println("Atomic计数器:");
System.out.println("最终计数: " + counter.getCount());
System.out.println("耗时: " + (endTime - startTime) + "ms");
}
}
在高并发场景下,原子类通常比synchronized具有更好的性能表现,特别是在竞争激烈的情况下。原子类避免了线程阻塞和上下文切换的开销。