一、什么是CAS?
CAS(Compare And Swap),即比较并交换,是一种用于实现多线程同步的原子操作指令。
二、工作原理
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。CAS 指令执行时,当且仅当内存位置的值与预期原值相等时,将内存位置的值修改为新值,否则什么都不做。这个过程是原子性的,即这个操作在执行过程中不会被中断。
例如,在一个多线程环境下,多个线程同时尝试修改一个共享变量。线程首先读取共享变量的当前值作为预期原值,然后在进行修改时,使用 CAS 操作将共享变量的当前值与预期原值进行比较,如果相等,则将共享变量的值更新为新值,否则重新读取共享变量的值并尝试再次修改,直到成功为止。
三、优点
非阻塞性:与传统的锁机制不同,CAS 操作在执行失败时不会阻塞线程,而是让线程重新尝试,避免了线程阻塞和唤醒带来的开销,提高了系统的并发性。
原子性:CAS 操作是原子性的,能够保证在多线程环境下对共享变量的操作是安全的,不会出现数据不一致的情况。
四、缺点
ABA 问题:如果一个值从 A 变为 B,又从 B 变回 A,那么使用 CAS 操作时可能无法察觉这个变化,从而导致错误的结果。解决这个问题的方法通常是使用带有版本号的原子类,每次修改时版本号加一,这样就可以避免 ABA 问题。
解决:
① 使用 AtomicStampReference 类,增加了一个标记 stamp,可以判断数据有没有被修改过。
public class AtomicStampedReference<V> {
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
② 可以使用版本号,其实和上面stamp原理差不多。
循环开销大:在高并发的情况下,如果多个线程同时竞争一个共享变量,可能会导致 CAS 操作不断失败,从而使线程进入长时间的循环等待,消耗大量的 CPU 资源。
五、应用场景
CAS 操作在 Java 中的 java.util.concurrent.atomic 包下被广泛应用,例如 AtomicInteger、AtomicLong、AtomicReference 等原子类都是通过 CAS 操作来实现线程安全的。这些原子类在多线程环境下可以安全地进行自增、自减、赋值等操作,而不需要使用传统的锁机制。此外,CAS 操作还可以用于实现无锁数据结构,如无锁栈、无锁队列等。