hello啊,各位观众姥爷们!!!本baby今天又来报道了!哈哈哈哈哈嗝🐶
面试官:CAS都有哪些问题?如何解决?
CAS 的问题及解决方案
CAS(Compare and Swap)是一种高效的无锁并发机制,但在实际使用中需注意以下问题及其解决方案:
1. ABA 问题
问题描述
- 场景:线程 1 读取共享变量值为
A
,此时线程 2 将值修改为B
,后又改回A
。 - 后果:线程 1 执行 CAS 时认为值未被修改(仍为
A
),操作成功,但实际变量已被其他线程修改过,可能导致逻辑错误。
解决方案
- 版本号机制:为变量附加一个版本号(或时间戳),每次修改递增版本号,CAS 需同时检查值和版本号。
- Java 实现:
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0);// 更新时检查值 + 版本号 int oldStamp = ref.getStamp(); int newValue = 200; ref.compareAndSet(100, newValue, oldStamp, oldStamp + 1);
2. 自旋开销(CPU 资源浪费)
问题描述
- 场景:高并发下多个线程频繁竞争同一变量,CAS 失败后线程会循环重试(自旋),导致 CPU 资源浪费。
- 后果:大量线程自旋时,CPU 利用率飙升,系统吞吐量下降。
解决方案
- 自旋优化策略:
- 限制自旋次数:设定最大自旋次数,超过后改用锁或阻塞。
- 退避算法:每次失败后增加等待时间(如指数退避)。
- 示例代码(退避):
int maxRetries = 10; int retries = 0; while (!atomicInt.compareAndSet(oldValue, newValue)) {if (retries++ > maxRetries) {// 退化为锁机制synchronized(lock) { ... }break;}Thread.sleep(1 << retries); // 指数退避 }
3. 只能保证单个变量的原子性
问题描述
- 场景:需对多个共享变量进行原子更新时,CAS 无法直接实现。
- 例如:转账操作需同时修改“账户 A 余额”和“账户 B 余额”。
解决方案
- 合并变量:将多个变量封装为一个对象,用
AtomicReference
原子更新整个对象。 - 锁机制:对复合操作使用锁(如
synchronized
或ReentrantLock
)。 - 示例代码(合并变量):
class AccountPair {int balanceA;int balanceB; }AtomicReference<AccountPair> pairRef = new AtomicReference<>(new AccountPair());// 原子更新 AccountPair oldPair = pairRef.get(); AccountPair newPair = new AccountPair(oldPair.balanceA - 100, oldPair.balanceB + 100); pairRef.compareAndSet(oldPair, newPair);
4. 公平性问题
问题描述
- 场景:CAS 是非公平的,无法保证等待时间最长的线程优先执行。
- 后果:高竞争下某些线程可能长期无法获取资源(饥饿现象)。
解决方案
- 队列调度:结合队列机制(如 AQS 的 CLH 队列),按 FIFO 顺序分配资源。
- 公平锁:使用
ReentrantLock
的公平模式,确保先到先得。
5. 硬件平台限制
问题描述
- 场景:某些硬件(如旧 CPU 或嵌入式设备)不支持 CAS 指令。
- 后果:无法直接使用 CAS,需依赖软件模拟(性能较差)。
解决方案
- 软件模拟:通过锁或操作系统提供的原子 API 实现类似功能。
- 平台适配:使用高层并发库(如 Java 的
java.util.concurrent
),屏蔽底层差异。
总结
问题 | 解决方案 | 适用场景 |
---|---|---|
ABA 问题 | 版本号(AtomicStampedReference ) | 需严格检查变量历史状态 |
自旋开销 | 退避算法、限制自旋次数、改用锁 | 高竞争场景 |
多变量原子性 | 合并变量或使用锁 | 复合操作需求 |
公平性 | 队列调度或公平锁 | 需避免线程饥饿 |
硬件限制 | 软件模拟或高层并发库 | 不支持 CAS 的平台 |
你想要的技术资料我全都有:https://pan.q删掉汉子uark.cn/s/aa7f2473c65b