CAS分析原理+乐悲观锁
CAS 指的是现代 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。这个指令会对内存中的共享数据做原子的读写操作。简单介绍一下这个指令的操作过程:首先,CPU 会将内存中将要被更改的数据与期望的值做比较。然后,当这两个值相等时,CPU 才会将内存中的数值替换为新的值。否则便不做操作。最后,CPU 会将旧的数值返回。
简单的来说,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则返回V。这是一种乐观锁的思路,它相信在它修改之前,没有其它线程去修改它;而Synchronized是一种悲观锁,它认为在它修改之前,一定会有其它线程去修改它,悲观锁效率很低。
原理代码示例
代码如下:
import java.util.concurrent.atomic.AtomicInteger;
/*
CAS是什么?==>compareAndSet 比较交换
*/
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(6);
//compareAndSet(int expect,int update) 如果期望值expect与工作内存中的值相同则修改 否则修改失败
//与期望值相同 atomicInteger修改为2021
System.out.println(atomicInteger.compareAndSet(6,2021)+"\t current data is "+atomicInteger.get());//true
//上述操作atomicInteger修改为2021与期望值6不同 所以修改失败
System.out.println(atomicInteger.compareAndSet(6,2022)+"\t current data is "+atomicInteger.get());//false
}
}
结果如下:
![](https://img.haomeiwen.com/i28430736/c09c4089898688a4.png)
分析:首先我们new的atomicInteger对象传的值是6,compareAndSet(int expect,int update)是底层的参数,第一组传的值是compareAndSet(6,2021),这里的expect=6与atomicInteger=6的值对比后发现一样,则返回true,把atomicInteger的值更新为2021,同理第二组传的值是compareAndSet(6,2022),这里的expect=6与atomicInteger=2021的值对比后发现不一样,则返回false,atomicInteger的值不更新且仍然为2021
乐观锁与悲观锁
悲观锁(Pessimistic Lock)
顾名思义,就是很悲观,假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。
乐观锁(Optimistic Lock)
顾名思义,就是很乐观,假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。乐观锁不能解决脏读的问题。每次拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
悲观锁在Java中的使用,就是利用各种锁。如synchronized 乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。
独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁用到的机制就是CAS,Compare and Swap。
缺点与优点
CAS优点
高效解决原子操作
CAS缺点
CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作
1.ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
2. 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
3. 只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
以上就是Android必备技术中的并发模型技术的CAS,想要进阶更多Android技术,往高级工程师目标出发可以参考我这个《Android核心进阶技术手册》点击获取哦!
![](https://img.haomeiwen.com/i28430736/b878b5988ca32148.png)
小结
CAS和锁都解决了原子性问题,和锁相比,由于其非阻塞的,它对死锁问题天生免疫,并且,线程间的相互影响也非常小。更为重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,因此,他要比基于锁的方式拥有更优越的性能。
但是,CAS真的有那么好吗?又到挑刺时间了!
要让我们失望了,CAS并没有那么好,主要表现在三个方面:
- 1、循环时间太长
- 2、只能保证一个共享变量原子操作
- 3、ABA问题。