1. 什么是CAS?从CPU指令到并发基石
CAS(Compare-And-Swap)是计算机科学中实现并发控制的原子操作,它包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。当且仅当V的值等于A时,处理器才会用B更新V的值,否则不执行任何操作。无论哪种情况,都会返回V的旧值。
这个看似简单的操作,实际上是现代多线程编程的基石。我第一次在Java的AtomicInteger中接触CAS时,曾疑惑为什么不用synchronized这种更"直观"的方式。直到在高并发场景下看到锁竞争导致的性能断崖式下跌,才明白CAS的价值。
关键区别:synchronized是悲观锁,CAS是乐观锁。前者假定冲突必然发生,后者假定冲突很少发生。
CAS的实现依赖于CPU的原子指令。以x86架构为例,CMPXCHG指令就是CAS的硬件实现。当多个线程同时执行CAS时,CPU会通过缓存锁定或总线锁定确保原子性。这也是为什么CAS比锁更高效——它避免了用户态到内核态的切换开销。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. CAS的工作原理与ABA问题
2.1 CAS操作的三步流程
- 读取阶段:获取内存当前位置的值作为预期值A
- 比较阶段:检查当前内存值是否仍等于A
- 交换阶段:如果相等,则原子性地更新为B;否则重试或放弃
这个过程在Java的Unsafe类中对应着:
java复制public final native boolean compareAndSwapInt(
Object o, long offset, int expected, int x);
2.2 经典的ABA问题
假设线程1读取内存值为A,此时线程2将A→B→A。当线程1执行CAS时,会错误地认为值未被修改过。这在链式结构中尤为危险——可能导致链表断裂。
解决方案:
- 版本号标记(如AtomicStampedReference)
- 布尔标记(如AtomicMarkableReference)
我在实际项目中遇到过ABA导致的缓存一致性问题:使用CAS更新缓存时,由于版本号未及时更新,导致脏数据被误认为有效。后来通过引入时间戳+版本号的双重校验才解决。
3. CAS在Java并发包中的应用
3.1 Atomic类族的实现
以AtomicInteger为例,其核心实现是:
java复制private volatile int value;
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// Unsafe中的实现:
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
3.2 CAS与ReentrantLock的对比
| 特性 | CAS | ReentrantLock |
|---|---|---|
| 实现方式 | 乐观锁 | 悲观锁 |
| 阻塞情况 | 自旋 | 线程挂起 |
| 适用场景 | 低冲突 | 高冲突 |
| 内存开销 | 单个变量 | 维护等待队列 |
| 公平性 | 无法保证 | 可配置公平/非公平 |
在秒杀系统实践中,我们发现:当并发线程数超过CPU核心数2倍时,CAS的自旋开销会超过锁。此时采用分段CAS(如ConcurrentHashMap的实现)效果更好。
4. CAS的典型应用场景
4.1 无锁计数器实现
这是CAS最直观的应用:
java复制class CASCounter {
private AtomicInteger value = new AtomicInteger(0);
public int increment() {
int v;
do {
v = value.get();
} while (!value.compareAndSet(v, v + 1));
return v + 1;
}
}
4.2 无锁栈的实现
通过AtomicReference实现栈顶指针的原子更新:
java复制class ConcurrentStack<E> {
AtomicReference<Node<E>> top = new AtomicReference<>();
public void push(E item) {
Node<E> newHead = new Node<>(item);
Node<E> oldHead;
do {
oldHead = top.get();
newHead.next = oldHead;
} while (!top.compareAndSet(oldHead, newHead));
}
}
4.3 数据库乐观锁
在分布式系统中,CAS思想同样适用。比如MySQL的乐观锁实现:
sql复制UPDATE products
SET quantity = quantity - 1, version = version + 1
WHERE id = 1 AND version = 2;
5. CAS的性能优化实践
5.1 减少CAS冲突的三种策略
- 分散热点:像ConcurrentHashMap那样分段处理
- 退避策略:冲突时增加随机延迟
- 批量操作:合并多个操作为一个(如LongAdder)
5.2 伪共享问题的解决
由于CPU缓存行机制,看似无关的变量可能相互影响。例如:
java复制// 错误示例
class AtomicData {
volatile long value1;
volatile long value2; // 可能与value1在同一缓存行
}
// 正确做法:缓存行填充
class PaddedAtomicLong {
volatile long value;
long p1, p2, p3, p4, p5, p6; // 填充至64字节
}
在开发高频交易系统时,我们通过JOL工具发现两个AtomicLong存在伪共享,导致性能下降30%。添加填充后吞吐量恢复正常。
6. CAS的局限性及替代方案
6.1 CAS不适用的场景
- 涉及多个共享变量的复合操作
- 需要阻塞等待的条件操作
- 长时间的自旋会浪费CPU资源
6.2 更高级的并发工具
当CAS无法满足需求时,可以考虑:
- ReadWriteLock:适合读多写少场景
- StampedLock:乐观读模式进一步减少冲突
- CompletableFuture:异步编程模型
在物联网设备状态同步项目中,我们最初尝试用CAS维护设备状态机,后来发现状态转换过于复杂,最终改用Actor模型才优雅解决。
