一、CAS是什么?
CAS(Compare And Swap) 可以理解为 “先检查再动手” 的机智操作。它的核心逻辑是:在修改一个值之前,先确认这个值是否和预期一致,如果一致才修改,否则放弃。这种操作是 原子性 的(不会被其他线程打断),所以不用加锁也能保证线程安全。
二、举个现实例子
假设你和朋友同时想修改共享文档里的一个数字:
-
你看到当前数字是 100,想改成 150。
-
CAS操作:文档系统会检查当前数字是否还是 100。
- 如果是 → 修改为 150,成功!
- 如果不是 → 放弃修改,告诉你“有人改过了,请重试”。
这样即使多人同时操作,也不会出现数据错乱。
三、Java中的CAS实现
Java通过 Unsafe类 调用CPU的原子指令(如x86的CMPXCHG),在以下场景广泛使用:
-
Atomic类(如
AtomicInteger、AtomicLong):AtomicInteger count = new AtomicInteger(0); count.incrementAndGet(); // 内部通过CAS实现自增 -
并发容器(如
ConcurrentHashMap):CAS用于无锁化更新。 -
锁机制(如
ReentrantLock):CAS实现锁的竞争。
四、CAS工作流程
以 AtomicInteger 的自增为例:
-
读取当前值(假设是 100)。
-
计算新值(100 + 1 = 101)。
-
执行CAS操作:
- 检查内存中的值是否还是 100。
- 如果是 → 更新为 101,返回成功。
- 如果被其他线程改成了 200 → 失败,重新读取并重试(自旋)。
五、CAS的优缺点
| 优点 | 缺点 |
|---|---|
| 无锁,性能高(避免线程阻塞) | ABA问题(后文详解) |
| 代码简洁,避免死锁 | 高并发下自旋可能浪费CPU资源 |
六、ABA问题与解决方案
1. ABA问题是什么?
- 场景:线程A将值从 100 → 200 → 100,线程B的CAS操作看到值还是 100,误以为未被修改过。
- 风险:数据看似没变,但中间过程可能已被篡改(比如银行余额先扣款又退回,但消费记录丢失)。
2. 解决方案
-
AtomicStampedReference:给值加版本号(类似“时间戳”)。
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0); ref.compareAndSet(100, 150, 0, 1); // 检查值和版本号
七、适用场景
-
计数器(如网站访问量统计):
AtomicLong visitCount = new AtomicLong(0); visitCount.incrementAndGet(); // CAS实现自增 -
无锁数据结构(如无锁队列、无锁栈)。
-
状态标记(如线程池的状态控制)。
八、总结
CAS 是并发编程的利器:
- 优势:轻量、高效,避免锁开销。
- 注意:ABA问题需警惕,高竞争场景可能不如锁高效。
口诀:
「CAS 操作真巧妙,先查后改保可靠
无锁并发性能高,ABA问题要记牢
Atomic类里显身手,版本号来解烦恼!」