什么是CAS?

326 阅读3分钟

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

介绍

CAS(Compare And Swap/Set)比较并交换, CAS 算法的过程是这样:它包含 3 个参数CAS(V,A,B)。 V 表示要更新的变量(内存值), A 表示预期值(原来从主物理内存上拷贝的值), B 表示新值(就是想要更新成的值)。当且仅当 V 值等于 A 值时,才会将 V 的值设为 B,如果 V 值和 A 值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS 返回当前 V 的真实值。

我们先来看下面这段代码

class MyTask {
    //int  tickets;
    static AtomicInteger tickets = new AtomicInteger(5);
    static int num = 5;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    tickets.getAndIncrement();
                    num++;
                }

            }).start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println("tickets=" + tickets);
        System.out.println("num=" + num);
    }

}

运行结果:

tickets=10005

num=9505

可以知道AtomicInteger可以保证原子性

对AtomicInteger的compareAndSet进行分析

java.util.concurrent包中借助* CAS实现了区别于synchronized同步锁的一种乐观锁,使用这些类可以保证一些操作的原子性,AtomicInteger类compareAndSet通过原子操作实现了CAS操作,最底层是基于汇编语言实现的,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做

修改成功的例子

class MyTask1 {
    //int  tickets;
    static AtomicInteger tickets = new AtomicInteger(5);

    public static void main(String[] args) {
        boolean res = tickets.compareAndSet(5, 2021);
        System.out.println(res);
        System.out.println(tickets.get());
    }

}

运行结果:

true

2021

修改失败的例子

class MyTask1 {
    //int  tickets;
    static AtomicInteger tickets = new AtomicInteger(5);

    public static void main(String[] args) {
//        boolean res = tickets.compareAndSet(5, 2021);
        tickets.getAndIncrement();
        System.out.println(tickets.get());
        boolean res = tickets.compareAndSet(5, 123);
        System.out.println(res);
        System.out.println(tickets.get());
    }

}

运行结果:

6

false

6

这边compareAndSet有两个参数一个是except期望值,一个是update更新值,此时main线程从主物理内存中拷贝一个tickets = 5,在执行compareAndSet(5, 2021)会经历这样一个过程:

  1. 让except的值和主物理内存中tickets的实际值比较
  2. 如果相等就把拷贝来的tickets执行更新 update=2021并且写回到主物理内存中,返回true
  3. 如果不相等就说明在这个过程中主物理内存中的值已经被其他线程修改过了,那么拷贝新的值到工作内存中。返回false表示原来的更新操作失败。

compareAndSet源码部分:

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

compareAndSwapInt源码部分:

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

可以看到这个compareAndSwapInt方法有native说明不是Java语言实现的

  • this:传入对象
  • valueOffset:对象在内存中偏移量为valueOffset位置的值
  • except:期望值
  • update:更新值

读取传入对象var1在内存中偏移量为offset位置的值与期望值expected作比较。相等就把更新值赋值给offset位置的值。方法返回true。不相等,就取消赋值,方法返回false。

这也是CAS的思想,及比较并交换。用于保证多线程情况下的数据安全性。