Concurrent Program——深入理解CAS

CAS的全称为CompareAndSet,比较并设置。

CAS可以说是并发编程的基石,很多实现并发的方式底层都是依赖于CAS操作。

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。CAS的语义是“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”。

CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。是一条CPU的原子指令,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的。

cas是如何保证自己是原子操作的,它在底层是lock cmpxchg执行(x86架构)。当某个线程执行到lock指令时,cpu会让总线锁住,当这个核把指令执行完毕后,在开启总线。这个过程不会被线程的调度机制打断。所以是原子的。

cas操作需要与volatile配合使用,满足cas的类型会将共享变量设置为volatile的,因为volatile保证的变量的可见性,这样cas操作才可以获取到共享变量的最新值。与之前的锁比较,cas保证了原子性,配合volatile保证了可见性。

拿出AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。

private volatile int value;

在没有锁的机制下可能需要借助volatile原语,保证线程间的数据是可见的(共享的)。

这样才获取变量的值的时候才能直接读取。

public final int get() {
    return value;
}

然后来看看++i是怎么做到的。

public final int incrementAndGet() {

    for (;;) {//无限循环直到成功

        int current = get();

        int next = current + 1;

        if (compareAndSet(current, next))//相同返回true,不同返回false

            return next;
    }

}

在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止

而compareAndSet利用JNI来完成CPU指令的操作。

public final boolean compareAndSet(int expect, int update) {  

    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。

这种以无锁方式保证临界变量正确性与有锁的效率比起来,无锁会更高一点。因为有锁的情况下,当线程没有获得到共享资源时,会进入阻塞状态,就会进行上下文切换,比如一辆车要进行停车,熄火,再启动。这样耗费的时间就会更长。但无锁情况下,虽然也需要while(true)循环一直检查,但cpu始终属于当前线程,线程就可以一直高速运行下去。但这也是保证此线程能分到cpu时间片的情况下,所以要保证核心数的充足,即进行cas操作的线程数不要超过核心数。

Java提供的一个cas的封装Atomic类,提供了一些原子操作

比如AtomicInteger的自增操作,底层会调用unsafe类的cas操作,比如AtomicBoolean,AtomicInteger,AtomicLong

CAS的ABA问题

假设有两个线程,线程1读取到内存值A,线程1时间片用完,切换到线程2,线程2也读取到了内存值A,并把它修改为B值,然后再把B值还原到A值,简单说,修改次序是A->B->A,接着线程1恢复运行,它发现内存值还是A,然后执行C A S操作,这就是著名的ABA问题,但是好像又看不出什么问题。

再举一个生活中的例子:朋友钱包的钱,我偷偷拿走了100,晚上再趁朋友没注意放100到里面,算不算偷窃?肯定算是吧

ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前版本号是否等于预期版本号,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值