CAS 优缺点
乐观锁和悲观锁,你站哪一边?
synchronized ──► 悲观派:先加锁再干活,万一有人跟我抢呢
CAS ──► 乐观派:先试试,没人抢我就赢了,有人抢就重来没有银弹,只有权衡。
CAS 的优势
1. 非阻塞
没有锁的挂起和唤醒,线程不会进入阻塞状态。
java
public class CASAdvantageDemo {
private final AtomicInteger counter = new AtomicInteger(0);
// CAS:无锁累加
public void increment() {
int expected;
do {
expected = counter.get();
} while (!counter.compareAndSet(expected, expected + 1));
}
// synchronized:有锁累加
private int syncCounter = 0;
public synchronized void syncIncrement() {
syncCounter++;
}
public static void main(String[] args) throws InterruptedException {
CASAdvantageDemo demo = new CASAdvantageDemo();
// 测试 CAS
long start = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
demo.increment();
}
System.out.println("CAS 耗时: " + (System.nanoTime() - start) / 1_000_000 + "ms");
// 测试 synchronized
start = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
demo.syncIncrement();
}
System.out.println("synchronized 耗时: " + (System.nanoTime() - start) / 1_000_000 + "ms");
}
}在低到中等竞争下,CAS 通常更快。
2. 无死锁
锁的最坏情况是死锁——两个线程互相等待对方释放资源。
CAS 不存在这个问题,因为它只操作一个变量。要么成功,要么重来,没有"占有并等待"的可能。
3. 性能优异
无竞争时,CAS 就是一条 CPU 指令,毫秒级完成。
CAS 的劣势
1. ABA 问题
值从 A 变成 B 又变回 A,CAS 无法检测。
java
public class ABAProblemDemo {
private final AtomicReference<String> ref = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
ABAProblemDemo demo = new ABAProblemDemo();
Thread t1 = new Thread(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
boolean success = demo.ref.compareAndSet("A", "C");
System.out.println("线程1 CAS 结果: " + success);
});
Thread t2 = new Thread(() -> {
demo.ref.compareAndSet("A", "B");
demo.ref.compareAndSet("B", "A");
});
t2.start();
Thread.sleep(50);
t1.start();
t1.join();
t2.join();
System.out.println("最终值: " + demo.ref.get());
System.out.println("问题:线程1 以为读到了 A,但 A 已被其他线程修改过");
}
}2. 高竞争下的自旋
当大量线程同时竞争同一个变量时,CAS 会频繁失败,CPU 空转。
java
public class CASContentionDemo {
private final AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
CASContentionDemo demo = new CASContentionDemo();
ExecutorService executor = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
for (int j = 0; j < 1000; j++) {
demo.counter.incrementAndGet();
}
});
}
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("结果: " + demo.counter.get());
System.out.println("高竞争下,CAS 可能导致大量自旋重试");
}
}解决方案:LongAdder 分段累加,减少竞争。
3. 复合操作需要循环 CAS
简单的 value++ 用 CAS 实现:
java
// ❌ 不原子
public boolean incrementIfEven() {
int current = value.get();
if (current % 2 == 0) {
return value.compareAndSet(current, current + 1); // 检查和修改之间可能被修改
}
return false;
}
// ✅ 原子
public boolean incrementIfEvenCorrect() {
while (true) {
int current = value.get();
if (current % 2 != 0) {
return false;
}
if (value.compareAndSet(current, current + 1)) {
return true;
}
// CAS 失败,重试
}
}4. 只能保证单个变量
java
// ❌ 这不是原子操作
public void transfer(Account from, Account to, int amount) {
if (from.balance >= amount) {
from.balance -= amount; // 非原子
to.balance += amount; // 非原子
}
}
// 需要加锁
public synchronized void transfer(...) { ... }实战选择
java
public class CASVsSyncSelection {
// 低竞争:CAS 更快
private final AtomicInteger lowContention = new AtomicInteger(0);
// 高竞争复合操作:synchronized
private final Object highContentionLock = new Object();
private int highContentionValue = 0;
public void simpleIncrement() {
lowContention.incrementAndGet(); // CAS
}
public synchronized void complexOperation() {
// 多个变量需要保持一致,必须用锁
}
}总结对比
| 特性 | CAS | synchronized |
|---|---|---|
| 阻塞 | 非阻塞 | 阻塞 |
| 死锁 | 无 | 可能 |
| 性能 | 无竞争时极快 | 有竞争时稳定 |
| 复杂度 | 需处理重试 | 简单 |
| 粒度 | 单变量 | 任意代码块 |
| ABA | 需额外处理 | 不存在 |
选择原则:
简单计数 ──► AtomicLong / LongAdder
标志位 ──► AtomicBoolean
引用更新 ──► AtomicReference
高竞争复合操作 ──► synchronized
低竞争复合操作 ──► 循环 CAS
需要版本追踪 ──► AtomicStampedReference记住:没有最好的方案,只有最适合当前场景的方案。
