我们是否遇到这样的场景:多个线程同时操作共享变量,如何保证数据的一致性和正确性?传统的锁机制虽然能解决问题,但往往会带来性能瓶颈。今天,我们来聊聊一个更优雅的解决方案------CAS(Compare And Swap)。
什么是CAS?先从一个实际问题说起
假设我们在开发一个电商系统,需要实现一个计数器来统计商品的浏览次数。在高并发场景下,多个用户同时浏览商品,如果简单地使用 count++
操作,很可能会丢失一部分计数。
csharp
// 这样的代码在并发环境下是不安全的
public class ViewCounter {
private int count = 0;
public void increment() {
count++; // 非原子操作,存在竞态条件
}
}
这时候,CAS就派上用场了。CAS是一种无锁的原子操作,它包含三个参数:
- 内存地址(V) :要更新的变量位置
- 期望值(A) :我们认为变量当前应该是什么值
- 新值(B) :想要设置的新值
CAS的核心思想很简单:"如果变量的值确实是我期望的那个值,那就更新它;否则,说明其他线程已经修改过了,我就不更新了"。
CAS的工作机制
让我们通过一个具体的例子来理解CAS的工作过程:
基本工作流程

多线程竞争场景

为什么选择CAS?优势和局限性
CAS的优势
在实际项目中,我发现CAS相比传统锁有几个显著优势:
- 性能更好:没有线程阻塞和唤醒的开销
- 避免死锁:不存在锁的获取和释放,天然避免死锁
- 硬件支持:现代CPU都提供了专门的CAS指令
但也要注意这些问题
- ABA问题:值从A变成B又变回A,CAS检测不到中间的变化
- 自旋开销:高竞争时可能会一直重试,消耗CPU
- 只能保护单个变量:无法实现复杂的原子操作
CAS的底层实现
硬件层面:CPU指令
在x86架构中,CAS操作依赖于 CMPXCHG
指令:
ini
# x86汇编中的CAS指令
CMPXCHG [内存地址], 新值
# 如果 EAX寄存器的值 == [内存地址]的值
# 则 [内存地址] = 新值,并设置ZF标志位
# 否则 EAX = [内存地址]的值,清除ZF标志位
Java中的实现:从Unsafe到现代API
Java早期通过 sun.misc.Unsafe
类提供CAS操作:
arduino
// 这是AtomicInteger内部的实现原理
public class AtomicInteger {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile int value;
static {
// 获取value字段在对象中的内存偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
实战:自己实现一个CAS计数器
让我们从零开始实现一个线程安全的计数器:
csharp
public class CASCounter {
private volatile int count = 0;
private static final Unsafe unsafe = getUnsafe();
private static final long countOffset;
static {
try {
countOffset = unsafe.objectFieldOffset
(CASCounter.class.getDeclaredField("count"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void increment() {
int current;
do {
current = count; // 读取当前值
// 尝试CAS更新,如果失败就重试
} while (!unsafe.compareAndSwapInt(this, countOffset, current, current + 1));
}
public int get() {
return count;
}
}
CAS的实际应用场景
1. 高性能计数器
在我之前的项目中,需要统计API调用次数,最初使用synchronized,后来改为AtomicLong:
csharp
public class APICounter {
private final AtomicLong requestCount = new AtomicLong(0);
public void recordRequest() {
requestCount.incrementAndGet(); // 内部使用CAS实现
}
public long getRequestCount() {
return requestCount.get();
}
}
2. 无锁数据结构:栈的实现
CAS的一个典型应用是实现无锁数据结构,比如栈:
ini
public class LockFreeStack<T> {
private volatile Node<T> top;
private static class Node<T> {
final T data;
volatile Node<T> next;
Node(T data) { this.data = data; }
}
public void push(T item) {
Node<T> newNode = new Node<>(item);
Node<T> currentTop;
do {
currentTop = top;
newNode.next = currentTop;
} while (!compareAndSetTop(currentTop, newNode));
}
public T pop() {
Node<T> currentTop;
Node<T> newTop;
do {
currentTop = top;
if (currentTop == null) return null;
newTop = currentTop.next;
} while (!compareAndSetTop(currentTop, newTop));
return currentTop.data;
}
}
3. 缓存更新策略
在缓存系统中,我们经常需要原子地更新缓存值:
csharp
public class Cache<K, V> {
private final ConcurrentHashMap<K, AtomicReference<V>> cache = new ConcurrentHashMap<>();
public boolean updateIfMatch(K key, V expectedValue, V newValue) {
AtomicReference<V> ref = cache.get(key);
return ref != null && ref.compareAndSet(expectedValue, newValue);
}
}
ABA问题及解决方案
ABA问题的真实案例
在一次项目中,我遇到了经典的ABA问题。假设我们有一个链表,两个线程同时操作:
csharp
// 问题场景:链表头部的CAS操作
public class LinkedListHead<T> {
private volatile Node<T> head;
public boolean removeHead() {
Node<T> currentHead = head;
if (currentHead == null) return false;
// 在这里,其他线程可能删除了头节点,然后又添加了一个相同的节点
// 导致head看起来没变,但实际上链表结构已经改变
return compareAndSetHead(currentHead, currentHead.next);
}
}
版本号机制
Java提供了 AtomicStampedReference
来解决ABA问题:
ini
public class ABASafeCounter {
private final AtomicStampedReference<Integer> value =
new AtomicStampedReference<>(0, 0);
public void increment() {
while (true) {
int[] stampHolder = new int[1];
int currentValue = value.get(stampHolder);
int currentStamp = stampHolder[0];
int newValue = currentValue + 1;
int newStamp = currentStamp + 1;
if (value.compareAndSet(currentValue, newValue, currentStamp, newStamp)) {
break;
}
}
}
}
什么时候用CAS?
我做过一个简单的性能测试,比较了synchronized、CAS和LongAdder在不同并发场景下的表现:测试结果(10线程,每线程100万次操作)Synchronized: ~850ms;AtomicInteger: ~320ms ;LongAdder: ~180ms
选择建议

根据我的实践经验:
- 低并发场景:AtomicInteger等原子类是首选
- 高并发计数:LongAdder性能更好
- 复杂业务逻辑:还是用传统锁比较稳妥
- 无锁数据结构:CAS是核心工具
总结与思考
CAS作为现代并发编程的基石,为我们提供了一种优雅的无锁解决方案。在实际项目中,我发现:
- 理解原理很重要:知道CAS的工作机制,才能更好地选择和使用
- 注意使用场景:不是所有情况都适合CAS,要根据具体需求选择
- 关注性能表现:在高竞争场景下,要考虑使用优化版本如LongAdder
- 小心ABA问题:在某些场景下,版本号机制是必需的
最后,CAS的学习让我对并发编程有了更深的理解。它不仅仅是一个技术工具,更代表了一种编程思想:通过乐观的方式处理竞争,而不是悲观地加锁等待。
这种思想在分布式系统、数据库设计等领域都有广泛应用,值得我们深入学习和实践。希望这篇文章对大家理解CAS有所帮助。如果你在实际项目中遇到相关问题,欢迎在评论区讨论交流!