Java基础:CAS详解


问: 1、知道CAS么?是如何实现的?
2、如果有一个共享整型变量n,比如说是票数,每一次卖出都需要减1,使用n–可以么?是否会存在问题?

答:
CAS的全称是Compare and swap, 它是一条CPU并发原语

它的功能是判断内存某个位置的值是否是预期值,如果是就更改为最新的值,这个过程是原子的

CAS并发原语体现在JAVA语言就是sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。

CAS的作用之一,比如可以处理上一篇博文中volatile不保证原子性的问题,可以解决单个共享变量在多线程环境下n++的问题。

要想理解CAS的作用,首选要充分理解volatile的作用,不清楚的话,可以先看下这篇博文(Java基础:volatile详解),然后再来理解CAS,不然可能会有点看不懂,因为有些概念,在本文中就不再重复赘述了。

1、CAS详解

之前介绍了,volatile不保证原子性,因此多线程在执行n++操作时,存在问题,解决方法之一是使用AtomicInteger类来进行处理,接下来就来分析为什么使用AtomicInteger类就可以解决该问题。

1.1 理解AtomicInteger类的compareAndSet方法

首先查看AtomicInteger类的compareAndSet方法。还是以买票进行举例,初始票数为10,两个线程同时买票,假设线程1先买到票,线程2后买到票。

运行结果如下图。可以看到线程2返回的是false,也就是未成功买到票。

package com.koping.test;

import java.util.concurrent.atomic.AtomicInteger;

public class CasDemo {
    
    
    public static void main(String[] args) {
    
    
        AtomicInteger atomicInteger = new AtomicInteger(10);

        // 业务逻辑:n行代码

        // 线程1,抢到票就将票数减1
        System.out.println(atomicInteger.compareAndSet(10, 9) + "\t 现在票数为: " + atomicInteger.get());

        // 线程2,抢到票就将票数减1
        System.out.println(atomicInteger.compareAndSet(10, 9) + "\t 现在票数为: " + atomicInteger.get());
    }
}

在这里插入图片描述

通过上面的例子,再结合下面compareAndSet方法的调用关系,可以很好的理解。该方法就是判断该对象当前的值是否和expectedValue相等?
如果相等,就将当前对象的值设置为newValue,并返回true;
如果不相等,则返回false;
在这里插入图片描述
在这里插入图片描述

1.2 理解AtomicInteger类的getAndIncrement方法

接着再来分析,为什么使用AtomicInteger类的getAndIncrement方法,就可以在多线程情况下替换n++,并且不会存在原子性问题。

我们可以看下getAndIncrement方法的源码。
1、在183行,调用了Unsafe类的getAndAddInt方法:U.getAndAddInt(this, VALUE, 1);
  1.1 在unsafe类的getAndAddInt方法中,首先根据当前对象和偏移量获取目前主内存上最新的
    值,然后调用compareAndSetInt方法来判断是否成功修改值?
    如果成功加1,就跳出循环;
    如果不成功,就再进入循环,重新获取主内存的最新值,然后再次进行判断。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

到这里,优先要先充分理解下什么是Unsafe类,这个非常重要!

1、Unsafe类是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问。但是Unsafe类却可以直接操作特定内存的数据

Unsafe类存在与sun.misc包中,该类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务

2、getAndAddInt方法中的offset,表示该变量值在内存中的偏移地址,而Unsafe类就是根据内存偏移地址获取数据的。
3、AtomicInteger类的value值用volatile修饰了,因此保证了多线程之间的可见性。
在这里插入图片描述
4、CAS的全称是Compare and swap,它是一条CPU并发原语
它的功能是判断内存某个位置的值是否是预期值,如果是就更改为最新的值,这个过程是原子的

CAS并发原语体现在JAVA语言就是sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。

由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成的,用户完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断。因此CAS是一条CPU原子指令,不会造成所谓的数据不一致问题

1.3 使用AtomicInteger类替代n++的分析

结合1.2中对AtomicInteger类的getAndIncrement方法的分析,现在应该就很好理解,为什么多线程情况下,可以使用AtomicInteger类的getAndIncrement方法来替代n++了。

因为getAndIncrement方法会调用Unsafe类的CAS方法,而该方法是一个CPU原子指令,不会被中断,因此多线程情况下,能保证n++后的结果一定正确。因此可以解决volatile不保证原子性的问题。

同时再查看getAndAddInt方法,可以看到,它是在循环中先获取主内存中该对象最新的值,然后通过CAS来保证替换的值和之前一致,也就是说是当前线程之前替换和写回主内存。

如果失败了,说明其他线程已经修改了该对象的值,那么就需要重新获取然后然后再写回主内存。

在这里插入图片描述
zuo

2、CAS的缺点

CAS的缺点主要有3个:
1)循环时间长,开销很大。可能线程1每次要执行CAS操作时,发现值都修改了,然后一直在循环里。
2)只能保证一个共享变量的原子操作。如果有多个共享变量的操作的话,最终还是要用synchronized。
3)ABA问题。
比如该对象初始值是5
1)线程1准备将值要改为6
2)此时线程2抢占到了CPU,将值改为了8
3)接着线程3抢占到了CPU,又将值改为了6
此时线程1终于抢占到了CPU,然后使用CAS发现原始值和当前值都是6,就可以执行操作。但是实际是在这个过程中,其他线程都进行了操作,可能存在逻辑上的其他异常,这就是ABA问题。

猜你喜欢

转载自blog.csdn.net/xueping_wu/article/details/124571158