并发编程之CAS原理的理解和适用场景

2,576 阅读4分钟
Discipline is the first condition of liberty  
纪律是自由的第一条件


CAS的英文为Compare and Swap 意为比较并交换

CAS加volatile 关键字是实现并发包的基石,没有CAS就不会有并发包,synchronized是一种悲观锁,而CAS指令实现了一种区别于synchronized的一种乐观锁

乐观锁和悲观锁

乐观锁

1.version方式

通过程序实现,具体实现是,数据库表中有一个版本字段,第一次读的时候获取到这个字段。处理完业务逻辑开始更新的时候,需要再次查看该字段的值是否和第一次的一样。如果一样更新,反之拒绝。之所以叫乐观,因为这个模式没有从数据库加锁。

2.CAS方式

Compare and Swap 涉及到三个参数

执行函数:CAS(V,E,N)

内存中的原数据V

旧的预期值E

需要修改的新值N

在对变量进行计算之前(如:++操作),首先读取原变量值,称为旧预期值E

更新之前在获取当前内存中的值,称为当前内存值V

如果 E == V 说明变量从未被其他线程修改过,此时将会写入新值N

如果 E != V 说明变量已被其他线程修改过,当前线程什么也不做,直接返回,是一个自旋操作,不断重试


实例代码分析:
  1. AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据Java内存模型,线程A和线程B各自持有一份value的副本,值为3。
  2. 线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。
  3. 线程B也通过getIntVolatile(var1, var2)方法获取到value值3,运气好,线程B没有被挂起,并执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为2。
  4. 这时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值(3)和内存的值(2)不一致,说明该值已经被其它线程提前修改过了,那只能重新来一遍了。
  5. 重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。
CAS的ABA问题及解决方案

假设这样一种场景,当第一个线程执行CAS(V,E,U)操作,在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值,这样的话,我们就无法正确判断这个变量是否已被修改过,如下图


这就是典型的CAS的ABA问题,一般情况这种情况发现的概率比较小,可能发生了也不会造成什么问题,比如说我们对某个做加减法,不关心数字的过程,那么发生ABA问题也没啥关系。但是在某些情况下还是需要防止的,那么该如何解决呢?在Java中解决ABA问题,我们可以使用以下两个原子类 (原文地址:blog.csdn.net/javazejian/…)


悲观锁

是读取的时候为后面的更新加锁,之后再来的读操作都会等待阻塞挂起。这种是数据库锁
乐观锁优点程序实现,不会存在死锁等问题。他的适用场景也相对乐观。阻止不了除了程序之外的数据库操作。
悲观锁是数据库实现,他阻止一切数据库操作,Synchronized的思想也是悲观锁(详见上一篇)
再来说更新数据丢失,所有的读锁都是为了保持数据一致性。乐观锁如果有人在你之前更新了,你的更新应当是被拒绝的,可以让用户从新操作。悲观锁则会等待前一个更新完成。这也是区别。具体业务具体分析

总结

使用CAS在线程冲突严重的情况下,会大幅降低程序性能,CAS适用于线程冲突较少的情况下使用