核心思想:乐观锁(假设冲突少,先操作再重试;冲突时自动重试,不阻塞线程)
1. CAS 基础原理
-
Compare and Swap(比较并交换):
-
比较内存中某变量的当前值与期望值,若相等则原子性地更新为新值(否则重试)。
-
通俗比喻:
像抢红包:你看到红包余额是
100(期望值),抢到后发现余额变成105(实际值),于是放弃当前操作(重试),再试一次。
-
-
关键依赖:
-
volatile:保证变量可见性(线程修改后立即写回主内存,其他线程能立刻看到)。 -
为什么需要
volatile?若不加
volatile,线程可能从本地缓存读旧值(如线程A修改了count=1,但线程B的缓存仍是count=0)。
-
2. 乐观锁 vs 悲观锁(重点!)
| 对比维度 | 乐观锁(CAS) | 悲观锁(synchronized) |
|---|---|---|
| 核心思想 | 最乐观:假设冲突少,先操作,冲突时重试。 | 最悲观:假设冲突多,先加锁,阻塞其他线程。 |
| 执行流程 | 1. 读值 → 2. 计算新值 → 3. CAS更新(失败则重试) | 1. 加锁 → 2. 操作共享变量 → 3. 解锁(其他线程等待) |
| 线程状态 | 不阻塞(线程持续重试,CPU空转但不挂起) | 阻塞(线程等待锁释放,进入 WAITING 状态) |
| 适用场景 | 竞争不激烈(如多核CPU下轻量级操作) | 竞争激烈(如高并发写入,避免频繁重试) |
| 效率影响 | 竞争少 → 效率高(无锁开销);竞争多 → 效率低(频繁重试消耗CPU) | 竞争少 → 效率低(锁竞争开销大);竞争多 → 效率高(避免无效重试) |
| 典型例子 | AtomicInteger.incrementAndGet() | synchronized 修饰的 count++ |
💡 为什么 CAS 是“乐观”?
它默认“别人不会改我的数据”,即使改了也只重试一次(像抢票系统:先尝试下单,失败了再重试,不卡住)。
为什么 synchronized 是“悲观”?
它默认“别人会改我的数据”,所以先锁住数据(像图书馆借书:锁住书架,别人不能碰)。
3. CAS 底层实现:Unsafe 类
-
Unsafe:JVM 提供的底层工具类(
sun.misc.Unsafe),直接调用 CPU 指令(如cmpxchg)。 -
为什么高效?
操作系统指令在 CPU 层级完成,无 JVM 介入,避免了线程切换开销(比
synchronized快 2-5 倍)。
示例代码(Unsafe 使用,仅作原理演示,生产中用原子类):
import sun.misc.Unsafe;
public class CASUnsafeDemo {
private volatile int value = 0;
private static final Unsafe unsafe = getUnsafe(); // 获取Unsafe实例(需反射,非生产推荐)
private static final long valueOffset = unsafe.objectFieldOffset(CASUnsafeDemo.class.getDeclaredField("value"));
public static void main(String[] args) {
CASUnsafeDemo demo = new CASUnsafeDemo();
// 尝试将 value 从 0 更新为 1,失败则重试
boolean success = demo.compareAndSet(0, 1);
System.out.println("Update success: " + success); // 输出 true
}
// CAS 操作:比较当前值是否等于 expect,是则更新为 update
public boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
private static Unsafe getUnsafe() {
try {
java.lang.reflect.Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
throw new RuntimeException("Get Unsafe failed", e);
}
}
}
注释:
Unsafe是 JVM 内部类,生产代码避免直接使用(需反射,不安全)。compareAndSwapInt是底层 CAS 指令封装,保证原子性。
4. 原子操作类:JUC 中的 CAS 实现
-
作用:
java.util.concurrent.atomic包提供线程安全类(如AtomicInteger),底层用 CAS + volatile。 -
为什么比
synchronized好?无锁竞争,低延迟、高吞吐(适合高并发计数器、状态标记)。
示例代码(AtomicInteger 实际用法):
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicDemo {
private AtomicInteger counter = new AtomicInteger(0); // 原子计数器
public void increment() {
// 线程安全自增:底层调用 CAS(无锁)
counter.incrementAndGet();
}
public static void main(String[] args) {
AtomicDemo demo = new AtomicDemo();
// 模拟多线程
for (int i = 0; i < 1000; i++) {
new Thread(() -> demo.increment()).start();
}
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("Final count: " + demo.counter.get()); // 输出 1000(正确)
}
}
注释:
incrementAndGet()本质是CAS操作:int expect = counter.get(); int update = expect + 1; counter.compareAndSet(expect, update); // 重试直到成功无需
synchronized,避免线程阻塞,适合高频操作。
关键总结
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 轻量级计数(竞争少) | AtomicInteger | 无锁、低延迟(CAS + volatile) |
| 高并发写入(竞争激烈) | synchronized | 避免频繁重试,锁开销固定 |
| 需要复杂同步逻辑 | ReentrantLock | 比 synchronized 更灵活 |
✅ 为什么 CAS 不是万能?
- 竞争激烈时:重试次数爆炸 → CPU 空转 → 效率低于
synchronized。- ABA 问题:值从 A→B→A,CAS 误认为未变(可用
AtomicStampedReference解决)。
JVM 设计哲学:无锁(CAS)是“默认最优解”,但需根据场景选择。