【JAVA】CAS总结

什么是CAS

CAS的全称为Compare-And-Swap,直译就是对比交换。是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用。

简单解释:CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。

CAS操作是原子性的,所以多线程并发使用CAS更新数据时,可以不使用锁。

CAS的优缺点

优点:

  1. 由于CAS是非阻塞的,可避免死锁,线程间的互相影响非常小。
  2. 没有锁竞争带来的系统开销,也没有线程间频繁调度的开销。

缺点:

  1. 可能自旋循环时间过长。如果某个线程通过CAS方式操作某个变量不成功,长时间自旋,则会对CPU带来较大开销。怎么解决:限制自旋次数。
  2. 只是一个变量的原子性操作,不能保证代码块的原子性。
  3. ABA问题。

CAS底层原理

  1. java 的 cas 利用的的是 unsafe 这个类提供的 cas 操作。
  2. unsafe 的cas 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg
  3. Atomic::cmpxchg 的实现使用了汇编的 cas 操作,并使用 cpu 硬件提供的 lock信号保证其原子性

CAS 可能会导致什么问题?

CAS存在ABA问题:

  • 当前线程只能感知共享变量的值有没有改变,假设别人从A改成了B,又改成了A,则当前线程感知不到,即ABA问题

  • 可以使用版本号机制解决ABA问题,如原子类AtomicStampedReference,加入了版本号,每当修改值,版本号就会+1

  • 如 AtomicStampedReference 加入了版本号,每当修改值,版本号就会+1。这样根据版本号就知道有没有别的线程修改过,而且还能知道修改过几次

  • 再如 AtomicMarkableReference,他的版本号是一个boolean类型,所以只能判断是否修改过值,不知道修改过几次。

    AtomicMarkableReference只能缓解ABA问题,如果别人修改标记后,又改回来,则无法感知。

CAS 机制可以高效地实现原子操作,但仍不完美:

  1. 循环时间长开销大:CAS 大量失败后长时间占用 CPU 资源,加大了系统性能开销
  2. 只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性
  3. ABA 问题:CAS 机制本质上依赖值有没有发生变化的操作条件。但是如果值原来是 A、被改成变成了 B、最后又变回了 A,那么使用 CAS 进行检查时会发现它的值没有发生变化进行了操作,但是实际上却变化了,这其实违背了约定的条件。

Java的JUC包里有没有现成的类可以解决CAS的ABA问题?

  • 原子引用:(乐观锁思想) AtomicStampedReference 类可以解决ABA问题。这个类维护了一个【版本号】Stamp,其实有点类似乐观锁的意思。在进行 CAS 操作的时候,不仅要比较当前值,还要比较版本号。只有两者都相等,才执行更新操作。
/**
 * @description: 解决 CAS 带来的 ABA问题
 */
public class ABADemo2 {
	//6为传入的值,1为版本号
    static AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(6, 1);

    public static void main(String[] args) {

        //使用版本号机制来验证ABA问题
        new Thread(() -> {
            //获取当前版本号
            int stamp = atomic.getStamp();
            System.out.println("线程A1的版本号为:" + stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
			//如果当前引用 等于 预期值并且 当前版本戳等于预期版本戳, 将更新新的引用和新的版本戳到内存
			//compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp);
            atomic.compareAndSet(6, 10, atomic.getStamp(), atomic.getStamp() + 1);
            System.out.println("线程A2的版本号为:" + atomic.getStamp());

            System.out.println(atomic.compareAndSet(10, 6, atomic.getStamp(), atomic.getStamp() + 1));
            System.out.println("线程A3的版本号为:" + atomic.getStamp());

        }, "线程A:").start();

        //使用【乐观锁】思想解决ABA问题
        new Thread(() -> {
            int stamp = atomic.getStamp();
            System.out.println("线程B1的版本号为:" + stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(atomic.compareAndSet(6, 2, stamp, stamp + 1));
            System.out.println("线程B2的版本号为:" + atomic.getStamp());

        }, "线程B:").start();
    }
}

  • AtomicStampedReference<>(6, 1); 这两个参数不要填的太大,因为我们本次测试用的是 Integer 类型
  • Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实例,而不是 new,因为 valueOf 使用缓存,而 new一定会创建新的对象分配新的内存空间。

为什么会导致ABA问题?

这是因为 CAS 算法是在某一时刻取出内存值然后在当前的时刻进行比较,中间存在一个时间差,在这个时间差里就可能会产生 ABA 问题。

Java哪些地方使用了CAS?

Java提供的API中使用CAS的地方有很多,比较典型的使用场景有原子类、AQS、并发容器

  • 对于原子类,以AtomicInteger为例,它的内部提供了诸多原子操作的方法。如原子替换整数值、增加指定的值、加1,这些方法的底层便是采用操作系统提供的CAS原子指令来实现的。
  • 对于AQS,在向同步队列的尾部追加节点时,它首先会以CAS的方式尝试一次,如果失败则进入自旋状态(在队列中自旋),并反复以CAS的方式进行尝试。此外,在以共享方式释放同步状态时,它也是以CAS方式对同步状态进行修改的。
  • 对于并发容器,以ConcurrentHashMap为例,它的内部多次使用了CAS操作。
    • 在初始化数组时,它会以CAS的方式修改初始化状态,避免多个线程同时进行初始化。
    • 在执行put方法初始化头节点时,它会以CAS的方式将初始化好的头节点设置到指定槽的首位,避免多个线程同时设置头节点。在数组扩容时,每个线程会以CAS方式修改任务序列号来争抢扩容任务,避免和其他线程产生冲突。
    • 在执行get方法时,它会以CAS的方式获取头指定槽的头节点,避免其他线程同时对头节点做出修改。

CAS的实现离不开操作系统原子指令的支持,Java中对原子指令封装的方法集中在Unsafe类中,包括:原子替换引用类型、原子替换int型整数、原子替换long型整数。这些方法都有四个参数:var1、var2、var4、var5,其中var1代表要操作的对象,var2代表要替换的成员变量,var4代表期望的值,var5代表更新的值。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值