JUC: CAS

Compare And Swap, 比较并交换,实现并发算法时常用的一种技术。

它包含三个操作数:

  • 内存位置
  • 预期原值
  • 更新值

执行CAS操作的时候,将内存位置的值与预期原值进行比较

  • 如果相等,那么处理器会自动将该位置的值更新
  • 如果不匹配,处理器不做任何操作,多线程同时执行CAS只会有一个成功

CAS是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-更新的原子性它是非阻塞的且自身具有原子性,也就是说这玩意效率更高且通过硬件保证,说明这玩意更可靠。
CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。

执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现独占的,比起用synchronized重量级锁,这里的排他时间要短很多,所以在多线程情况下性能会比较好。

1 Unsafe类

Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsfe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
注意: Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务

JVM提供的CAS机制,会在汇编层级禁止变量两侧的指令优化, 然后使用cmpxchg指令比较并更新变量值(原子性)

2 AtomicReference

public class CASTest {
    public static void main(String[] args) {
        AtomicReference<User> userAtomicReference = new AtomicReference<>();
        User tom = new User("tom",12);
        User jack = new User("jack", 43);
        userAtomicReference.set(tom);
        // 原子引用对象比较的是对象地址
        System.out.println(userAtomicReference.compareAndSet(tom, jack) + " : " + userAtomicReference.get().toString()); 


    }
}
class User{
    String name;
    int age;
    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return this.name + " " + this.age;
    }
}

3 CAS与自旋锁

CAS 是实现自旋锁的基础,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果,至于自旋呢,看字面意思也很明白,自己旋转。是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU资源。

CAS 是实现自旋锁的基础,自旋翻译成人话就是循环,一般是用一个无限循环实现。这样一来,一个无限循环中,执行一个CAS 操作,
当操作成功返回 true 时,循环结束;
当返回 false 时,接着执行循环,继续尝试 CAS 操作,直到返回 true。

// AtomicInteger.compareAndSet对应的c++源码
/**/ 
public final int getAndAddInt(Object o, long offset, int delta){
    int v;
    do {
        v = getIntVolatile(o,offset);
    } while(!compareAndSwapInt(o,offset,v,v + delta));
    return v;
}
// 自定义自旋锁
class SpinLockDemo{
    
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    
    public void lock(){
        Thread thread = Thread.currentThread();
        while(!atomicReference.compareAndSet(null,thread)) {
            // 自定义自旋 ==> 空转
        }
    }
    
    public void unlock(){
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
    }
}

4 CAS缺点

  • 自旋操作使cpu空转,浪费CPU资源
  • ABA问题:线程一从内存V中获取数据A,线程二将A修改成了B,然后又修改成了A,线程一进行CAS操作发现内存中仍是A,预期ok,线程一操作成功。

解决ABA问题:加版本号

// AtomicStampedReference

public class ABATest {
    public static void main(String[] args) {
        Book book1 = new Book("book1", "author1");
        Book book2 = new Book("book2", "author2");
        AtomicStampedReference<Book> bookAtomicStampedReference = new AtomicStampedReference<>(book1, 1); // 初始化需要版本号
        System.out.println(bookAtomicStampedReference.getReference() + " " + bookAtomicStampedReference.getStamp());
        bookAtomicStampedReference.compareAndSet(book1, book2, 1, 2);  // 添加版本号
        System.out.println(bookAtomicStampedReference.getReference() + " " + bookAtomicStampedReference.getStamp());
    }
}
posted @ 2025-09-29 08:43  飞↑  阅读(13)  评论(0)    收藏  举报