CAS 有什么问题?如何解决这些问题?
面试资料大全|各种技术资料-2000G
一、ABA 问题:最隐蔽的数据一致性问题
问题本质
- 危险场景:链表头指针变更
初始: Head -> NodeX 线程1: 准备CAS(Head, NodeX, NodeY) 线程2: 删除NodeX → 添加NodeZ → 重新添加NodeX 结果: Head指向已被删除的NodeX
解决方案
1. 版本号机制(推荐)
// Java 的 AtomicStampedReference
AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0); // 初始值+版本号int[] stampHolder = new int[1];
String current = ref.get(stampHolder);
int currentStamp = stampHolder[0];// 更新时检查值和版本号
boolean success = ref.compareAndSet(current, "C", currentStamp, currentStamp + 1
);
2. 时间戳机制
struct TaggedPointer {void* ptr;uint64_t timestamp; // 纳秒时间戳
};bool tagged_CAS(TaggedPointer* dest, TaggedPointer expected, TaggedPointer new_val) {if (dest->ptr == expected.ptr && dest->timestamp == expected.timestamp) {*dest = new_val;return true;}return false;
}
3. 垃圾回收语言的特殊方案
// 利用GC特性避免ABA(仅限托管环境)
AtomicReference<Object> ref = new AtomicReference<>();// 每次修改创建新对象
Object current = ref.get();
Object newObj = new Object(); // 旧对象会被GC回收
ref.compareAndSet(current, newObj);
二、循环开销问题:高竞争下的性能灾难
问题表现
- 性能影响:
- CPU 使用率飙升至 100%
- 实际吞吐量急剧下降
- 影响同核心其他线程
解决方案
1. 指数退避策略
int failures = 0;
while (!casUpdate()) {failures++;long delay = (long) Math.pow(2, Math.min(failures, 10)); // 2^failuresLockSupport.parkNanos(delay * 1000); // 纳秒级休眠
}
2. 自适应自旋(JVM 内部优化)
// HotSpot JVM 实现伪代码
int spins = 0;
while (!tryLock()) {if (spins < MAX_SPINS) {spins++;Thread.onSpinWait(); // JDK9+ 提示CPU优化} else {park(); // 挂起线程}
}
3. LongAdder 分段计数(Java 8+)
// 高竞争计数器替代方案
LongAdder counter = new LongAdder();
counter.increment(); // 内部使用Cell[]分散竞争// 实现原理
┌───────────┐ ┌─────┐
│ BaseValue ├───┬──►│Cell0│
└───────────┘ │ └─────┘├──►│Cell1││ └─────┘└──►│Cell2│└─────┘
三、单变量限制:复合操作的原子性缺失
问题场景
// 需要原子更新两个变量
class Account {int balance;int version;
}// 错误尝试:
AtomicReference<Account> ref = ...;
Account current = ref.get();
Account newAcc = new Account(current.balance+100, current.version+1);
ref.compareAndSet(current, newAcc); // 非原子创建对象!
解决方案
1. 对象打包法
// 创建不可变对象
class State {final int x;final int y;State(int x, int y) { ... }
}AtomicReference<State> ref = new AtomicReference<>(new State(0, 0));State current = ref.get();
State newState = new State(current.x+1, current.y-1);
ref.compareAndSet(current, newState);
2. 锁+CAS混合模式
// 对复合操作加锁
Lock lock = new ReentrantLock();
AtomicInteger x = new AtomicInteger(0);
AtomicInteger y = new AtomicInteger(0);void updateBoth() {lock.lock();try {int curX = x.get();int curY = y.get();// 复合操作x.compareAndSet(curX, curX+1);y.compareAndSet(curY, curY-1);} finally {lock.unlock();}
}
3. 事务内存(实验性)
// Java 的 LVar (Laboratory Virtual Machine)
@AtomicMethod
public void transfer(Account from, Account to, int amount) {from.balance -= amount;to.balance += amount;
}
四、额外问题:内存顺序与可见性
问题表现
- 危险场景:
// 线程1 data = 42; // 普通写 ready.compareAndSet(false, true); // CAS写// 线程2 if (ready.get()) { // CAS读System.out.println(data); // 可能看到旧值! }
解决方案:内存屏障
// Java volatile/CAS 自带内存屏障
AtomicInteger ready = new AtomicInteger();// 线程1
data = 42; // StoreStore屏障
ready.compareAndSet(0, 1); // 包含StoreLoad屏障// 线程2
if (ready.get() == 1) { // 包含LoadLoad+LoadStore屏障System.out.println(data); // 保证看到data=42
}
五、综合解决方案对比
问题类型 | 解决方案 | 适用场景 | 性能影响 | 实现复杂度 |
---|---|---|---|---|
ABA问题 | 版本号(AtomicStampedReference) | 指针/引用类型更新 | 低 | 中 |
时间戳 | 系统级编程 | 中 | 高 | |
GC依赖方案 | 托管环境(JVM/.NET) | 低 | 低 | |
循环开销 | 指数退避 | 中等竞争场景 | 可变 | 低 |
LongAdder分段 | 高竞争计数器 | 极低 | 低 | |
自适应自旋 | JVM内置优化 | 低 | 无需实现 | |
单变量限制 | 对象打包 | <5个字段的复合状态 | 中 | 中 |
锁+CAS混合 | 复杂事务操作 | 高 | 高 | |
事务内存 | 实验性/研究场景 | 极高 | 极高 |
六、最佳实践指南
1. 选型决策树
2. 性能优化技巧
// 1. 缓存行填充防止伪共享
@Contended // Java 8+ 注解
public class PaddedAtomicLong extends AtomicLong {public volatile long p1, p2, p3, p4, p5, p6 = 7L;
}// 2. 批量操作减少CAS次数
class BatchedUpdater {private final AtomicLong counter = new AtomicLong();private ThreadLocal<Long> buffer = ThreadLocal.withInitial(() -> 0L);public void increment() {buffer.set(buffer.get() + 1);if (buffer.get() > 1000) { // 批量提交counter.addAndGet(buffer.get());buffer.set(0L);}}
}// 3. 热点分离
Map<Long, AtomicLong> shardedCounters = new ConcurrentHashMap<>();
void increment(long userId) {AtomicLong counter = shardedCounters.computeIfAbsent(userId % 16, k -> new AtomicLong());counter.incrementAndGet();
}
3. 架构级解决方案
面试资料大全|各种技术资料-2000G