什么是CAS机制
CAS(Compare-And-Swap,比较并交换)是一种无锁的原子操作,它是现代处理器提供的一种基础同步原语。CAS操作包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。
CAS的语义:如果内存位置V的值等于预期原值A,则将该位置更新为新值B,否则不进行任何操作。无论哪种情况,都会返回内存位置V的原始值。
CAS是实现无锁数据结构和算法的基础,它避免了传统锁机制可能导致的线程阻塞、上下文切换等开销。
CAS的工作原理
CAS操作的工作流程如下:
- 读取:从内存位置V读取当前值
- 比较:将读取的值与预期值A进行比较
- 交换:如果相等,将内存位置V的值更新为新值B
- 返回:返回操作是否成功
CAS操作示例
java
public class CASExample {
private volatile int value = 0;
private static final AtomicInteger atomicValue = new AtomicInteger(0);
// 使用AtomicInteger的CAS操作
public boolean increment() {
int current = atomicValue.get();
int next = current + 1;
// 如果当前值仍然是expected,则更新为next
return atomicValue.compareAndSet(current, next);
}
// 模拟CAS操作的实现
public synchronized boolean compareAndSet(int expected, int update) {
if (value == expected) {
value = update;
return true;
}
return false;
}
}
CAS的优缺点
优点
1. 无锁操作
java
// 使用CAS的无锁操作
public class LockFreeCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 无锁操作
}
}
// 传统的锁操作
public class LockCounter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized(lock) { // 需要获取锁
count++;
}
}
}
2. 避免线程阻塞
- 不会导致线程挂起和唤醒
- 减少上下文切换开销
- 提高系统吞吐量
3. 死锁免疫
- 不使用锁,不会发生死锁
- 简化并发程序设计
缺点
1. ABA问题
java
public class ABADemo {
private AtomicReference<String> reference = new AtomicReference<>("A");
public void abaExample() throws InterruptedException {
// 线程1
Thread t1 = new Thread(() -> {
String current = reference.get(); // 读取到"A"
try {
Thread.sleep(1000); // 模拟处理时间
} catch (InterruptedException e) {}
// 尝试将"A"改为"B"
boolean success = reference.compareAndSet(current, "B");
System.out.println("Thread1 CAS result: " + success);
});
// 线程2
Thread t2 = new Thread(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {}
// 将"A"改为"B"
reference.compareAndSet("A", "B");
// 再将"B"改回"A"
reference.compareAndSet("B", "A");
System.out.println("Thread2 completed A->B->A");
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
2. 循环时间长开销大
javascript
public class CASSpinDemo {
private AtomicInteger counter = new AtomicInteger(0);
// 在高竞争场景下,可能产生大量自旋
public void highContentionIncrement() {
int current;
do {
current = counter.get();
// 如果竞争激烈,这里会不断重试
} while (!counter.compareAndSet(current, current + 1));
}
}
3. 只能保证一个共享变量的原子操作
java
// CAS只能操作单个变量
public class SingleVariableCAS {
private AtomicInteger x = new AtomicInteger(0);
private AtomicInteger y = new AtomicInteger(0);
// 无法原子地同时更新x和y
public void updateBoth() {
x.incrementAndGet(); // 原子操作
y.incrementAndGet(); // 原子操作
// 但两个操作之间不是原子的
}
}
// 解决方案:使用AtomicReference包装对象
public class MultiVariableCAS {
static class Point {
final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
}
private AtomicReference<Point> pointRef =
new AtomicReference<>(new Point(0, 0));
public void updateBoth(int deltaX, int deltaY) {
Point current;
Point next;
do {
current = pointRef.get();
next = new Point(current.x + deltaX, current.y + deltaY);
} while (!pointRef.compareAndSet(current, next));
}
}
ABA问题及解决方案
ABA问题详解
ABA问题是指在CAS操作期间,值从A变为B,然后又变回A,CAS操作会认为值没有发生变化,但实际上值已经被修改过了。
解决方案1:AtomicStampedReference(版本号解决方案)
java
public class StampedReferenceDemo {
private AtomicStampedReference<String> stampedRef =
new AtomicStampedReference<>("A", 0);
public void solveABAProblem() throws InterruptedException {
// 线程1
Thread t1 = new Thread(() -> {
int[] stampHolder = new int[1];
String current = stampedRef.get(stampHolder);
int stamp = stampHolder[0];
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
// 使用版本号进行CAS
boolean success = stampedRef.compareAndSet(current, "B", stamp, stamp + 1);
System.out.println("Thread1 CAS with stamp: " + success);
});
// 线程2
Thread t2 = new Thread(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {}
int[] stampHolder = new int[1];
String current = stampedRef.get(stampHolder);
int stamp = stampHolder[0];
// A -> B
stampedRef.compareAndSet(current, "B", stamp, stamp + 1);
// B -> A (版本号已经改变)
stampedRef.compareAndSet("B", "A", stamp + 1, stamp + 2);
System.out.println("Thread2 completed A->B->A with version update");
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
简单来说就是,当线程一启动时,还未执行交换操作被休眠1000ms,此时线程二开始执行交换将A变为B再有B变为A此时的stemp为2,当A恢复执行后发现stemp不是预期的0所以输出
System.out.println("Thread1 CAS with stamp: false");
解决方案2:AtomicMarkableReference(boolean值解决方案和版本号类似)
java
public class MarkableReferenceDemo {
private AtomicMarkableReference<String> markableRef =
new AtomicMarkableReference<>("Initial", false);
public void useMarkableReference() {
// 获取值和标记
boolean[] markHolder = new boolean[1];
String current = markableRef.get(markHolder);
boolean currentMark = markHolder[0];
// 带标记的CAS操作
boolean success = markableRef.compareAndSet(
current, "New Value", currentMark, !currentMark);
System.out.println("CAS success: " + success);
}
}
总结
CAS(Compare-And-Swap)机制是Java并发编程中的重要技术,具有以下特点:
核心优势
- 无锁操作:避免线程阻塞和上下文切换
- 高性能:在低竞争场景下性能优异
- 死锁免疫:不会产生死锁问题
- 原子性保证:硬件级别的原子操作
主要挑战
- ABA问题:需要使用版本号或标记解决
- 自旋开销:高竞争场景下可能产生大量CPU消耗
- 单变量限制:只能保证单个变量的原子性
适用场景
- 计数器:如访问统计、序列号生成
- 状态标记:如完成状态、激活状态
- 无锁数据结构:如无锁队列、栈
- 缓存更新:如缓存的原子更新