飞道的博客

CAS原理详解

467人阅读  评论(0)

 本文包含知识点

  • CAS是什么?
  • CAS应用场景及原理
  • CAS的不足

 1.CAS是什么?

CAS是Compare And Swap的缩写,比较并更新,是非阻塞同步的实现原理,它是CPU硬件层面的一种指令,从CPU层面能保证"比较并更新"两个操作的原子性。CAS指令操作包括三个参数:内存值(内存地址值)V、预期值A、新值B,当CAS指令执行时,当且仅当预期值A和内存值V相同时,才更新内存值为B,否则就不执行更新,无论更新与否都会返回否会返回旧的内存值V,上述的处理过程是一个原子操作。用java代码等效实现一下CAS的执行过程:


  
  1. /**
  2. * @description CAS等价代码
  3. **/
  4. public class CASTest {
  5. //内存值
  6. private volatile int ramAddress;
  7. /**
  8. * @param exceptRecord 期望值
  9. * @return newRecord 新值
  10. **/
  11. public synchronized int compareAndSwap(int exceptRecord, int newRecord) {
  12. int oldRamAddress = ramAddress;
  13. if (oldRamAddress == exceptRecord) {
  14. ramAddress = newRecord;
  15. }
  16. return oldRamAddress;
  17. }
  18. }

 2.CAS应用场景及原理

  • 原子类:JUC包下的原子类,比如AtomicInteger原子类的incrementAndGet()方法
  • 并发容器:比如ConcurrentHashMap的put()方法
  • 乐观锁:比如数据库层面的乐观锁操作,我们通过版本号字段来实现

鉴于内存操作是非常敏感的,一不小心就可能搞挂机器,所以像CAS这样的CPU指令集去操作内存并没有向广大开发人员开放,在jdk中被封装在sun.misc.Unsafe类里面,主要体现在Unsafe类的compareAndSwapInt()、copareAndSwapLong()等几个方法包装提供,通过Unsafe工具类来直接操作内存。查看源码我们可以看到这几个方法都是native修饰符修饰的,表示本地方法,本地方法就是指方法的实现体不在当前文件,而是在用其它语言实现的文件中。下面以AtomicInteger原子类为例,简单剖析下源码:


  
  1. //volatile修饰value变量,保证线程间可见性
  2. private volatile int value;
  3. static {
  4. try {
  5. //拿到内存地址,根据这个地址得到具体的内存值
  6. valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField( "value"));
  7. } catch (Exception var1) {
  8. throw new Error(var1);
  9. }
  10. }
  11. public final int incrementAndGet() {
  12. return unsafe.getAndAddInt( this, valueOffset, 1) + 1;
  13. }
  14. public final int getAndAddInt(Object var1, long var2, int var4) {
  15. int var5;
  16. //无限循环重试预期值和旧内存值比较动作
  17. do {
  18. var5 = this.getIntVolatile(var1, var2);
  19. } while(! this.compareAndSwapInt(var1, var2, var5, var5 + var4));
  20. return var5;
  21. }
  22. //包装在Unsafe类中,从cpu层面保证原子性
  23. public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

 3.CAS的不足

CAS存在逻辑漏洞,称为ABA问题:当一个变量V初次读取时是A值,这个时候被线程1改为了B值,又被线程2修改回了A,那么在准备赋值的时检测到它还是A值,CAS就会误认为它没有改变过。显然为了解决这个问题,我们可以加入版本号的概念,通过控制变量值的版本来保证CAS的正确性,比如J.U.C包下的AtomicStampedReference就可以实现,但是一般情况下CAS的这个逻辑漏洞并不会影响程序并发问题。


 引申阅读:


转载:https://blog.csdn.net/fanrenxiang/article/details/104287656
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场