CAS(Compare And Swap)
CAS核心原理
操作流程
CAS 包含三个参数:内存值(V)、预期值(E)和新值(N)。执行步骤如下:
-
比较:检查当前内存值 V 是否等于预期值 E。
-
交换:如果相等,将内存值更新为新值 N;否则不操作。
-
返回结果:返回操作是否成功(或当前实际值)。整个过程是原子性的,由 CPU 指令直接保证,例如 Intel 的 cmpxchg 指令。
AtomicInteger atomicInt = new AtomicInteger(5); boolean success = atomicInt.compareAndSet(5, 10); // 若当前值为5,则更新为10 //若 atomicInt 的当前值未被其他线程修改,则更新成功(返回 true),否则失败(返回false)
原子性保障
CPU 通过两种机制保证 CAS 的原子性:
总线锁定
- 使用 LOCK# 信号锁定总线,阻止其他 CPU 访问内存,确保当前操作独占执行。
- 缺点:锁定期间其他 CPU 无法操作内存,性能开销大
缓存锁定
- 仅锁定缓存行(CPU 高速缓存的最小单位),通过 缓存一致性协议(如 MESI)检测共享变量是否被修改。
- 优势:缩小锁定范围,减少阻塞,现代 CPU 普遍采用此方式。
应用场景
原子类Atomic
//Java 的 AtomicInteger、AtomicReference 等类通过 CAS 实现线程安全的原子操作。
public final int incrementAndGet() {for (;;) {int current = get(); // 当前值int next = current + 1; // 新值if (compareAndSet(current, next)) return next; // 更新成功则返回}
}
//自旋循环中不断尝试 CAS,直到成功。
并发容器
- ConcurrentHashMap:初始化数组时通过 sizeCtl 变量和 CAS 确保只有一个线程执行初始化,其他线程自旋等待或调用 Thread.yield() 让出 CPU
- ConcurrentLinkedQueue:无锁队列通过 CAS 实现入队和出队的线程安全
自旋锁和无锁算法
//自旋锁:线程通过循环尝试 CAS 获取锁,避免阻塞和上下文切换。例如:
while (!lock.compareAndSet(0, 1)) { /* 自旋 */ }
//非阻塞数据结构:如无锁栈、队列等,依赖 CAS 实现并发安全
优缺点
ABA问题和解决方案
问题描述
- 线程 1 读取值 A,线程 2 将 A→B→A,线程 1 的 CAS 仍会成功,导致逻辑错误(例如链表头节点被意外删除)
解决方案
//版本号机制:每次更新递增版本号,检查值的同时检查版本号。
//AtomicStampedReference:Java 提供带版本号的原子类。
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 1);
ref.compareAndSet(100, 200, 1, 2); // 版本号从1→2
```[2](@ref) [4](@ref)
java中的cas实现
- Unsafe类:CAS 操作通过 Unsafe 类调用本地方法(如 compareAndSwapInt),直接操作内存地址
- 底层指令映射:JVM 将 compareAndSet 映射为 CPU 的 cmpxchg 指令,确保原子性
总结
- CAS 是一种高效的无锁并发控制机制,适用于计数器、轻量级锁等场景。其核心优势是避免线程阻塞,但需注意 ABA 问题和自旋开销。在高并发系统中,CAS 常与退让策略(指数退避)(如 Thread.yield())结合,平衡性能与资源消耗
- 指数退避:指数退避是一种动态调整重试延迟时间的算法,旨在通过指数级增长的等待时间和随机抖动减少系统资源竞争或网络冲突,提升系统的稳定性和效率。其核心思想是:在每次尝试失败后,延迟时间成倍增加,从而避免因频繁重试导致的资源过载或冲突加剧。
- 核心原理:延迟时间指数增长、随机抖动、最大延迟限制
- 延迟时间指数增长:初始设定一个最小延迟(如 100ms),每次失败后延迟时间按指数倍数(如 2 倍)增长。例如:第 1 次重试延迟 100ms,第 2 次 200ms,第 3 次 400ms,依此类推。
- 随机抖动(Jitter):在延迟时间中加入随机值(如 0~100ms),防止多个请求在同一时间点集中重试,避免“雪崩效应”
200ms,第 3 次 400ms,依此类推。 - 随机抖动(Jitter):在延迟时间中加入随机值(如 0~100ms),防止多个请求在同一时间点集中重试,避免“雪崩效应”
- 最大延迟限制:设置上限(如 30 秒),防止无限等待