深入理解CAS

什么是CAS ?

CAS(Compare-And-Swap),字面意思 “比较并交换” 。

CAS相较于互斥锁是一种轻量级锁,并且是一种乐观锁。

乐观锁与悲观锁

悲观锁

悲观锁就是持悲观态度的锁。每次去拿数据的时候认为别的线程也会同时修改数据,所以每次在拿数据的时候都会上锁,这样别的线程想拿到这个数据就会阻塞直到它拿到锁。

乐观锁

乐观锁就是持比较乐观态度的锁。就是在操作数据时非常乐观,认为别的线程不会同时修改数据,所以不会上锁,但是在更新的时候会判断在此期间别的线程有没有更新过这个数据。

CAS三个操作数

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

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

如果匹配,那么处理器会自动将该位置值更新为新值

如果不匹配,处理器不做任何操作或重来

多个线程同时执行CAS操作只有一个会成功,这种重来的行为叫自旋。

如何使用CAS

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        // public final boolean compareAndSet(int expect, int update)
        // 如果我期望的值达到了,那么就更新,否则,就不更新
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
        atomicInteger.getAndIncrement()
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

CAS底层原理

CAS是JDK提供的非阻塞原子性操纵,它通过硬件保证了比较-更新的原子性,更加可靠。

它的底层是一条原子指令(cmpxchg),不会造成数据不一致问题,Unsafe类提供的CAS方法(compareAndSwapXXX)底层实现都是CPU指令cmpxchg。

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

在这里插入图片描述

对Unsafe的理解

Unsafe类是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地方法来访问(native),Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部的方法操作可以像C的指针一样直接操作内存,因为Java中的CAS操作的执行依赖于Unsafe类的方法。

Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。

变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

变量value用volatile修饰,保证了多线程之间的内存可见性。

ABA 问题(狸猫换太子)

线程1读取了数据A,线程2也读取了数据A。
线程2通过CAS比较,发现是原数据A没错,于是就将数据A改为了数据B。
线程3此时通过CAS比较,发现原数据就是数据B,于是就将数据B改成数据A。
此时,线程1通过CAS比较,发现原数据是A,就改成了自己要改的值。
虽然说线程1最后能能操作成功,但是这样已经违背了CAS的初衷,数据已经被修改过了,按CAS的原则来讲,CAS是不应该修改成功的。

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger("A");
        // ============== 捣乱的线程 ==================
        System.out.println(atomicInteger.compareAndSet("A", "B"));
        System.out.println(atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet("B", "A"));
        System.out.println(atomicInteger.get());
        // ============== 期望的线程 ==================
        System.out.println(atomicInteger.compareAndSet("A","C"));
        System.out.println(atomicInteger.get());
    }
}

如何解规避ABA问题

可以设置一个自增的标志位,数据的每一次修改标志位都会自增,比较标志位的值,还可以加上时间戳,显示上一次修改的时间,比较时间戳的值。

例如原子引用

public class CASDemo {
    //AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
    // 正常在业务操作,这里面比较的都是一个个对象
    static AtomicStampedReference<Integer> atomicStampedReference = new
            AtomicStampedReference<>(1,1);
    // CAS compareAndSet : 比较并交换!
    public static void main(String[] args) {
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("a1=>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1);
            System.out.println("a2=>"+atomicStampedReference.getStamp());
            System.out.println(atomicStampedReference.compareAndSet(2, 1,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1));
            System.out.println("a3=>"+atomicStampedReference.getStamp());
        },"a").start();
        // 乐观锁的原理相同!
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("b1=>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 6,
                    stamp, stamp + 1));
            System.out.println("b2=>"+atomicStampedReference.getStamp());
        },"b").start();
    }
}

CAS的缺点

  • ABA问题
  • 只能保证一个共享变量的原子操作
  • 如果CAS自旋时间过长,会给CPU带来很大的开销
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值