掘金 后端 ( ) • 2024-04-27 07:57

CAS(Compare-And-Swap或Compare-And-Set)操作是一种重要的并发原语,广泛用于实现无锁编程中的原子操作。CAS操作包含三个基本操作数:内存位置(V)、预期原值(E)和新值(N)。其操作逻辑是:“只有当内存位置V的值与预期原值E相匹配时,才将内存位置V上的值更新为新值N”,这个过程是原子的。如果V的值不等于E,那么操作失败,内存位置V的值不会被更新。此外,CAS通常会返回操作是否成功的标志,有些实现还会返回当前内存位置V的实际值。

如何使得原子类工作

Java的原子类(如AtomicIntegerAtomicLong等),就是利用了CAS操作来实现线程安全的原子操作。这些类提供了一种机制,可以在没有锁的情况下进行线程安全的更新操作,从而在某些场景下提供比传统锁更好的性能。

AtomicInteger的例子

AtomicInteger为例,它是基于CAS操作实现的。下面是一个简化的AtomicIntegergetAndIncrement方法实现,展示了如何使用CAS操作。

public class AtomicInteger extends Number implements java.io.Serializable {
    private volatile int value;

    public final int getAndIncrement() {
        int current;
        do {
            current = value; // 当前值
            // CAS操作:比较当前值与内存中的值,如果相同,则更新为current+1
        } while (!compareAndSet(current, current + 1));
        return current;
    }

    public final boolean compareAndSet(int expect, int update) {
        // 假设这是一个原子操作,实际上这一步是由底层硬件指令实现的
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    // 其他方法略去...
}

getAndIncrement方法中,首先读取当前的value值,然后使用CAS操作尝试将其更新为value+1。如果在这个过程中value被其他线程修改了,compareAndSet方法会失败,循环会继续,直到CAS操作成功为止。这种机制确保了即使多个线程同时尝试更新同一个AtomicInteger,每次也只有一个线程能够成功,从而保证了操作的原子性。

CAS的优点

  1. 性能:在没有线程竞争的情况下,CAS的性能通常优于锁。因为CAS避免了线程的上下文切换和锁的开销。
  2. 可扩展性:对于高度竞争的环境,尽管CAS可能会导致大量的CPU循环等待,但它的性能通常仍然优于锁,因为锁可能导致线程挂起和上下文切换。

CAS的缺点

  1. ABA问题:如果变量V先从A变为B,然后又从B变回A,CAS操作无法识别出变量已经被修改过两次。
  2. 循环时间长开销大:在高度争用的情况下,CAS操作可能需要多次循环,这会增加CPU的消耗。
  3. 只能保证一个共享变量的原子操作:如果需要同时更新多个共享变量,就无法使用CAS实现。

为了解决ABA问题,一些原子类提供了带有版本号的CAS操作,如AtomicStampedReference,通过检查时间戳或版本号来避免ABA问题。

CAS操作是实现高性能并发控制的关键技术之一。通过精心设计,可以利用CAS操作构建出无锁的数据结构和算法,以提高并发系统的性能和可扩展性。