CAS介绍

CAS全称是Compare And Swap,它的实现和它的字面意思一样,先比较后交换,它是CPU硬件层面的一种指令,从CPU层面能保证"比较并更新"这一段操作的原子性。
与synchronized关键字比较不同是synchronized是一种悲观锁,CAS是一种乐观锁。

悲观锁和乐观锁

  • 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样当第二个线程想拿这个数据的时候,第二个线程会一直堵塞,直到第一个释放锁,他拿到锁后才可以访问。java中的synchronized关键字就是典型的悲观锁思想。
  • 乐观锁:乐观锁概念为每次拿数据的时候都认为别的线程不会修改这个数据,所以不会上锁,但是在更新的时候会判断一下在此期间别的线程有没有修改过数据,乐观锁适用于读操作多的场景。在Java中java.util.concurrent.atomic包下面的原子变量就是使用了乐观锁的一种实现方式CAS实现。

CAS实现原理

CAS有三个操作数,内存值V,旧的预期值A,要修改的更新值B,当A和V相同时将V修改成B,否则一直比较V和的值直至A和V相同为止,使用java代码大致实现逻辑如下:

public class CasDemo {
    private volatile Integer value;

    public boolean compareAndSwap(int except, int update) {
        if (value == except) {
            this.value = update;
            return true;
        }
        return false;
    }
}

value使用volatile修饰,保证了该变量的有序性和可见性,compareAndSwap方法两个入参,一个期望值(except),一个更新值(update),先判断value是否和期望值相等,相等则修改为更新值返回true表示修改成功,不相等说明已经被其他线程修改了,返回false修改失败

JDK中的CAS的实现

在JDK中的java.util.concurrent(JUC java并发工具包)就是建立在CAS之上的,我们以并发原子类AtomicInteger为例,先看源码(源码比较多,所以只保留了重点部分)

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    public final int get() {
        return value;
    }
    
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    
    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }
    
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

getAndIncrement(),getAndDecrement()方法分别相当于 i++, i– 的操作,compareAndSet则是上面比较并交换操作,可以看到它们都是调用了Unsafe对象的方法。

再看看Unsafe的部分源码

在这里插入图片描述

可以看到Unsafe类的方法全部是用native修饰的,属于本地方法。
由于Java 方法无法直接访问底层,因此无法直接进行内存操作,所以在jdk中内存操作都封装在sun.misc.Unsafe类里面,Java通过本地方法去操作内存。

再看AtomicInteger的compareAndSet方法,是调用Unsafe的compareAndSwapInt方法

在这里插入图片描述

这里有四个入参,expect和update容易理解,分别是预期值和更新值,valueOffset通过源码可知它是通过Unsafe的objectFieldOffset本地方法返回的一个数值

在这里插入图片描述在这里插入图片描述

因此,valueOffset大致可以理解为变量value在内存中的地址,Unsafe的compareAndSwapInt方法就是借助C来调用CPU底层,通过valueOffset获取value内存中的值并与expect比较,相同则更新为update返回true,不同则什么都不操作返回false。
java.util.concurrent包下的所有CAS操作都是基于这一系列原子操作.

Logo

Authing 是一款以开发者为中心的全场景身份云产品,集成了所有主流身份认证协议,为企业和开发者提供完善安全的用户认证和访问管理服务

更多推荐