CAS操作:Java 并发的无锁基石
CAS(Compare And Swap)是一种无锁算法,用于实现多线程环境下的原子操作。它通过比较内存中的实际值与预期值,仅在两者相等时才进行更新,从而避免了传统锁机制的开销。这种思想是 Java 并发包(java.util.concurrent)的核心之一,尤其在atomic包的原子类中广泛应用。
CAS 的核心原理与实现
CAS 操作涉及三个关键参数:
- V(内存地址):要操作的变量在内存中的位置;
- A(预期值):线程认为 V 处当前应该的值;
- B(新值):若 V 处的值等于 A,则将 V 更新为 B。
整个过程由 CPU 的原子指令(如 x86 的CMPXCHG)保证原子性,无需加锁。
AtomicInteger 中的 CAS 实现
以AtomicInteger为例,其compareAndSet方法的底层实现:
1 | public class AtomicInteger { |
Unsafe 类:CAS 的底层支撑
Unsafe是 JDK 提供的一个强大但危险的工具类,通过它可以直接操作内存和线程。其核心方法包括:
| 方法 | 作用 |
|---|---|
objectFieldOffset(Field) |
获取字段在对象中的内存偏移量 |
compareAndSwapInt(...) |
原子性地比较并替换 int 值 |
park(boolean, long) |
阻塞当前线程(可指定超时) |
unpark(Object) |
唤醒指定线程 |
如何使用 Unsafe?
由于安全限制,直接调用Unsafe.getUnsafe()会抛出异常。通常通过反射获取实例:
1 | public class TestUnsafe { |
CAS 的三大核心缺陷
尽管 CAS 高效,但存在以下局限性:
CPU 开销大
在高并发场景下,大量线程频繁尝试 CAS 操作却失败,会导致自旋(空循环),消耗 CPU 资源。
解决方案:
- 降低竞争:通过数据分片(如
LongAdder)分散热点; - 结合锁:竞争激烈时使用
synchronized或ReentrantLock。
只能保证单个变量的原子性
CAS 仅能对单个变量进行原子操作,无法扩展到多个变量或代码块。
解决方案:
- 使用
AtomicReference封装多个变量; - 使用锁机制(如
synchronized)保证代码块原子性。
ABA 问题
问题描述:变量值从 A 变为 B,再变回 A,CAS 认为值未变化,但实际中间发生了修改。
示例场景:
1 | // 线程1:期望将100改为200 |
解决方案:使用AtomicStampedReference或AtomicMarkableReference,为变量增加版本号或标记位:
1 | AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0); |
CAS 在 JDK 中的典型应用
1. Atomic 系列类
AtomicInteger、AtomicLong、AtomicBoolean等通过 CAS 实现原子操作,替代synchronized。
2. 并发容器
ConcurrentHashMap在 JDK 8 + 中使用 CAS 替代分段锁,提升并发性能。
3. AQS(AbstractQueuedSynchronizer)
ReentrantLock、Semaphore等基于 AQS 实现,其核心状态变量通过 CAS 更新。