学习一下CAS

场景回顾


去年我写过一篇处理系统中一个并发引起的bug提到了在高并发下如何避免数据状态不一致的问题,里面的解决方案其实已经用到了CAS的思想了。具体的场景请查看文章。


传统的锁


多线程并发修改一份共享数据的时候,通常都是使用加锁的办法,来保证共享数据的正确性和安全性。但是使用锁是有代价的。当一个线程占有了一份共享资源的锁后,其他的线程会被阻塞住,只能等待,啥事情没法干,万一拥有锁的那个线程正在等待一个I/O操作的处理结果,那么其他线程可能需要等待更长的时间。JVM实现阻塞的方式就是挂起线程,之后重新调度被挂起的线程,这样就造成了上下文切换,CPU的吞吐量就大大的降低了。线程应该是要干活的,而不是一直在等待工作或者等待别的线程释放锁


无锁


就像之前在处理系统中一个并发引起的bug一文提到的,不使用任何锁,同样可以在高并发的情况下,保证数据的正确性。这里其实就是CAS思想的体现了。

CAS

CAS的想法很简单,它包含3个参数CAS(V,E,N)

V:将要被更新的变量的值
E : 期望的值
N : 新值

只有当V=E的时候,才将N的值赋给V,否则说明其他线程已经修改过V的值了,已经捷足先登了,当前线程无需再做任何操作,直接退出返回当前V的值。

由于没有使用到锁,同时修改数据失败的线程也没有被挂起,以致变成等待状态。

CAS硬件指令实现

CAS是属于复合操作,如何保证这种【读(read)-改(update)-写(write)】操作的原子性呢?现在的CPU都有实现CAS指令,通过硬件的方式来保证CAS操作的原子性。

备注:
CPU底层实现其实也是要用到锁的,只不过这种复合操作的原子性由硬件来维持,应用层的代码无需关心而已。

AtomicInteger

AtomicInteger类中就有一个CAS操作:

public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset,expect,update);
}

compareAndSet方法利用JNI调用了操作系统的原生接口来达到修改数据的目的。


CAS的ABA问题


如果数据经历过如下变化:A–>B–A,这个时候,原来的线程看到数据还是A,跟预期的一样,就会直接做修改操作。在数据状态敏感的业务场景中,这样可能导致大故障的。

可以利用乐观锁的想法,加一个版本号来做区分。


CAS优缺点


优点
  • 无锁,就不存在死锁和线程竞争资源时被挂起的问题,提高了CPU的利用率,避免了大量的上下文切换开销
缺点

1、上面提到的ABA问题
2、只能保证操作一个共享变量的原子性,如果操作多个共享变量,则无法保证原子性(此时只能用锁的机制了)


参考文章


  1. 聊聊并发(五)——原子操作的实现原理
  2. 高并发Java(4):无锁
  3. JAVA CAS原理深度分析
  4. 非阻塞算法简介
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值