Java CAS(Compare-And-Swap)详解
CAS(Compare-And-Swap,比较并交换)是实现无锁并发的重要机制,被广泛应用于 Java 并发包(java.util.concurrent
) 中。它是一种基于硬件的原子操作,能够高效地解决多线程竞争问题。
1. 什么是 CAS?
CAS 的核心思想:
-
CAS 是一种原子操作,包含以下三个参数:
- 预期值(Expected Value, V):线程期望变量当前的值。
- 目标值(Target Value, A):变量在内存中的实际值。
- 更新值(New Value, B):线程希望将变量更新为的新值。
-
操作逻辑:
- 比较变量的预期值和内存中的实际值是否相等:
- 如果相等,则将变量的值更新为新值。
- 如果不相等,则说明该变量已被其他线程修改,更新失败。
- 比较变量的预期值和内存中的实际值是否相等:
伪代码
boolean CAS(AtomicReference<V> target, V expected, V newValue) {
if (target.value == expected) {
target.value = newValue;
return true; // 更新成功
} else {
return false; // 更新失败
}
}
2. CAS 在 Java 中的实现
Java 中的 CAS 操作是通过 Unsafe
类 的本地方法实现的,Unsafe
调用了 CPU 的原子指令(如 cmpxchg
)来完成 CAS 操作。
关键类和方法
-
Unsafe
类:- 位于
sun.misc
包中。 - 提供底层的 CAS 支持,常用方法:
compareAndSwapInt
compareAndSwapLong
compareAndSwapObject
- 位于
-
AtomicXXX
类:- Java 并发包中的
java.util.concurrent.atomic
提供了基于 CAS 的原子类:AtomicInteger
AtomicLong
AtomicReference
- Java 并发包中的
-
示例:
AtomicInteger
的 CAS 实现
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
valueOffset
:变量的内存偏移地址。unsafe.compareAndSwapInt
:调用底层硬件的 CAS 指令。
3. CAS 的优势
- 无锁并发:
- 不需要线程阻塞,避免了线程上下文切换的开销。
- 高性能:
- 相比传统锁机制,CAS 在高并发环境下性能更高。
- 线程安全:
- 基于硬件指令实现原子性,避免了数据竞争。
4. CAS 的应用场景
-
Atomic
原子类:- 提供对基本类型和对象引用的无锁原子操作。
- 示例:
AtomicInteger counter = new AtomicInteger(0); counter.incrementAndGet(); // 使用 CAS 实现自增
-
AQS(AbstractQueuedSynchronizer):
- Java 并发框架中实现锁(如
ReentrantLock
)的基础类,利用 CAS 来管理同步状态。
- Java 并发框架中实现锁(如
-
ConcurrentHashMap:
- 使用 CAS 实现无锁的高效并发操作。
-
线程池:
- 在任务队列或状态更新中使用 CAS 保证线程安全。
5. CAS 的局限性
5.1 ABA 问题
- 问题描述:
- 如果一个变量的值从
A
修改为B
,然后又改回A
,CAS 操作无法检测到这种变化,会误认为值未被修改。
- 如果一个变量的值从
- 解决方案:
- 使用 版本号机制 或 带时间戳的 CAS。
- Java 提供了
AtomicStampedReference
来解决 ABA 问题:AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 1); int stamp = ref.getStamp(); // 获取版本号 ref.compareAndSet(100, 200, stamp, stamp + 1); // CAS 操作同时更新版本号
5.2 循环开销大
- 问题描述:
- 如果 CAS 操作失败,通常会自旋重试(即不断尝试),在高竞争的情况下可能导致 CPU 开销过高。
- 解决方案:
- 使用 退避策略 或 限时自旋 来减少 CPU 开销。
5.3 只能保证一个变量的原子性
- 问题描述:
- CAS 只能对单个变量进行原子操作,无法直接保证多个变量的操作原子性。
- 解决方案:
- 使用锁(如
ReentrantLock
)来保证多个变量的同步操作。 - 使用
AtomicReference
来封装多个变量。
- 使用锁(如
6. CAS 的底层实现原理
6.1 底层硬件支持
CAS 依赖于 CPU 提供的原子指令集,如:
- x86 指令:
CMPXCHG
- ARM 指令:
LDREX
和STREX
这些指令在硬件层面保证了比较和交换操作的原子性。
6.2 JVM 的实现
-
Unsafe
类:- 使用 JNI 调用底层的 CAS 指令。
- 示例:
compareAndSwapInt
源码(伪代码表示):public final native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
-
内存屏障:
- JVM 会在 CAS 操作前后插入内存屏障(Memory Barrier):
- 保证指令不会被重排序。
- 保证变量的可见性。
- JVM 会在 CAS 操作前后插入内存屏障(Memory Barrier):
7. CAS 与锁的对比
特性 | CAS | 锁 |
---|---|---|
实现机制 | 无锁操作,依赖硬件指令实现原子性 | 基于操作系统的互斥机制,可能导致线程阻塞 |
性能 | 高,适合高并发场景 | 低,线程阻塞和上下文切换会降低性能 |
线程切换 | 不会导致线程切换 | 可能导致线程切换 |
复杂同步 | 只能处理单个变量的同步 | 可处理多个变量和复杂同步问题 |
公平性 | 不保证公平性 | 可以选择公平锁或非公平锁 |
8. CAS 示例代码
8.1 基本示例
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
public static void main(String[] args) {
AtomicInteger counter = new AtomicInteger(0);
// 使用 CAS 自增
boolean updated = counter.compareAndSet(0, 1);
System.out.println("CAS result: " + updated); // 输出:true
System.out.println("Counter value: " + counter.get()); // 输出:1
}
}
8.2 自定义实现 CAS
class SimulatedCAS {
private volatile int value;
public int getValue() {
return value;
}
public boolean compareAndSet(int expectedValue, int newValue) {
synchronized (this) {
if (value == expectedValue) {
value = newValue;
return true;
}
return false;
}
}
}
public class CustomCASExample {
public static void main(String[] args) {
SimulatedCAS cas = new SimulatedCAS();
cas.compareAndSet(0, 100);
System.out.println(cas.getValue()); // 输出:100
}
}
9. 总结
-
CAS 的优点:
- 高效、无锁的线程安全操作。
- 在高并发场景中避免了锁竞争,提高性能。
-
CAS 的缺点:
- 存在 ABA 问题。
- 循环开销大,适合竞争较少的场景。
- 无法直接处理复杂的同步逻辑。
-
适用场景:
- 高并发场景中简单的计数器、自旋锁、队列等。
- 使用
Atomic
类进行线程安全的基本操作。
通过合理使用 CAS,可以大幅提升并发程序的性能和效率,但需要注意其局限性,在适当的场景下结合锁或其他同步工具使用。