多线程编程CAS与CAS中的ABA问题

一、CAS:即compare and swap,比较和交换。虽说swap是交换的意思,但在这里也可以单纯的把swap理解为赋值。CAS是CPU提供的一条专门的指令,这个指令执行的操作就是比较和交换。

  • CAS的原子性:如果只是比较和交换这样的操作,我们平时写的代码也完全可以做到,那CAS有什么特殊的地方呢?我们知道,引起多线程编程线程安全问题的其中一个原因就是针对变量的操作不是原子性的,以变量的自增为例,我们平时写的count++,在CPU上执行时其实是三条指令,即从内存中读取变量到CPU寄存器上、变量自增、将变量存回内存。而线程调度的随机性就使得多个线程同时对一个变量进行自增时,有部分的操作就会失效。而CAS一条指令就能完成原来三条指令的操作,保证了操作的原子性,从而保证线程安全。在JAVA中,我们就可以通过Atomic的一些类来实现,典型的如AtomicInteger,这里简单演示一下通过AtomicInteger实现的线程安全的变量自增。

class Test {
    public static void main(String[] args) throws InterruptedException {
        //创建实例,并指定初始值为0
        AtomicInteger num = new AtomicInteger(0);
        Thread t1 = new Thread(()-> {
           for(int i = 0; i < 50_000; i++) {
               //通过循环和AtomicInteger中的方法实现自增
               num.getAndIncrement();
           }
        });
        Thread t2 = new Thread(()-> {
            for(int i = 0; i < 50_000; i++) {
                num.getAndIncrement();
            }
        });
        t1.start();
        t2.start();
        //控制main线程等待t1,t2结束
        t1.join();
        t2.join();
        System.out.println(num);
    }
}
  • 那么通过CAS实现的线程安全和synchronized加锁实现的线程安全有什么区别呢?

synchronized加锁实现的线程安全,其本质是让出现锁竞争的线程进入阻塞等待,这样虽然我们实现的是多线程代码,但执行起来却是多个线程串行执行,不能完全发挥多线程的速度优势,而CAS却是通过一条指令这样的原子性操作来保证线程安全,多个线程同时执行,不会因为要保证线程安全而进入阻塞等待,可以更好的发挥出多线程执行的速度优势。

  • 比较和交换:那这个比较和交换是什么和什么比较,又是什么之间在进行交换呢?是指内存中现有的值和原有的值进行比较,如果相同,就将修改过的值与原有的值进行交换。这样看可能不太好理解。 我们来举个例子:CPU第一次对内存A进行读取,读取到的值是x,这时读取到的我们就叫它原值,之后我们要对x进行修改,使用了一个CAS指令,那么CPU就会再去读取A中的值,然后拿这一次读取到的与上一次读取到的值进行比较,如果两个值相同,就将要改成的值与A中存着的x进行交换。这样可能还是不太好理解,我们可以再用一段伪代码来解释。

  • 通过判断内存中的值是否与原有值相等,来判断变量是否被改变过,如果被改变了,那么就不进行修改,如果没有被修改过,就执行交换操作。介绍完CAS的作用,以及CAS的执行过程之后,我们就可以开始说明ABA问题了。

二、ABA问题:在CAS的执行过程中,只要现有值与原有值相等,就视为没有发生改变,就会执行交换操作,但如果一个变量从A变为B,再从B变回A,那么变量实际上是发生了改变的,这种情况我们可能是不能再进行交换操作的,但CAS读取到的却是变量没有发生改变,就继续执行了交换操作。只是这样说可能看不出来继续执行交换会发生什么问题,我们举例子来说明。

说张三有一天在ATM上取钱,就在他确认要取100块钱时,ATM卡住了,张三等了一会儿,钱没取出来,于是张三又取了一次,而就在ATM执行第二次取钱的时候,张三的朋友李四把之前找张三借的100块还给张三了。这时,张三的账户就发生了这样的事情,第一次取款请求,张三账户余额100块,取钱100,账户扣款100,余额为0,但在扣款的时候,ATM卡住了,于是有了第二次扣款请求,原本第二次扣款请求应该看到的是余额为0,不进行扣款,但在这中间,李四又发了100块给张三,于是账户余额变成100,第二次扣款请求发现余额还有100,就又扣了1次。这其中,第一次扣款时,CAS读取原数据值为100,与现有值100相等,进行交换,将0交换到余额中,第二次扣款时的CAS本来应该将原数据100与0比较,发现不相等,就放弃交换,但在这当中,发生了一次100的入账,导致第二次CAS将后来的100与原有的100进行比较,发现相等,于是再次将0交换到余额中,导致张三被多扣了100块钱,这就是ABA问题。

要解决ABA问题,其实也很简单,只要CAS比较原有值和现有值时,增加一个版本号的比较。例如:我们可以设置一个共有的版本号,每一次CAS比较时,除了比较数据的值,还要比较版本号的值,如果相同,就进行交换,再让版本号的值加1,这样版本号的值只能增加,不会出现重复,及时出现ABA的情况,由于版本号不同,也不会造成重复交换。当然,这只是一个简单的例子,实际应用中,版本号的设计有很多不同的情况。

这些就是对CAS以及CAS中的ABA问题的简单介绍,并不能完整的说明和解释CAS的作用以及ABA问题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值