Java并发——CAS

CAS

1.CAS简介

CAS全称Compare And Swap,比较并交换。是一条CPU的原子指令,底层基于硬件中的汇编指令实现的。CAS算法涉及3个操作数内存值V预期原值A新值B,当内存值V等于预期值A时,更新内存值V为新值B。

CAS示例:

例如: 对于i = 0 , i++ 应用CAS思想,线程读取i的值0到内存中,预期原值A=0。

如果V等于A说明这个过程没有其他线程执行 i++ 操作,因此就修改V为新值B也就是1;

如果V不等于A说明有其他线程修改i的值,存在并发安全问题,放弃本次修改,重试。

CAS操作是原子性的,多线程并发使用CAS修改数据时,可以不用加锁。

2.CAS存在的问题

2.1ABA问题

CAS是检查预期原值A是否发生了变化,如果预期原值A变化为另一个值C,然后又修改回A,此时线程进行CAS比较会发现预期原值A没有变化,实际上发生了变化。

发生ABA问题的本质是CAS只检查初始和最终,而不关心中间状态的变化

为什么要解决ABA问题

假设一个场景:小明账户有100元,准备取50元,在多线程并发情况下,线程A和线程B都查余额为100,线程A执行成功取出50,正常情况下线程B应该失败,但是此时如果小明的账户收款50元,那么线程B在使用CAS进行比较时也会成功,又扣减了50元,结果就是小明取了50却扣减了100元。

解决ABA问题方案

使用版本号来标识变量的状态,比如A1修改为C2再修改回A3,此时A1和A3不相等就解决了ABA问题

使用AtomicStampedReference类来解决ABA问题

public class AtomicStampedReference<V> {

    private static class Pair<T> {
        final T reference;   // 对象引用
        final int stamp;    // 用于标志版本号
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
  / **
    *   ......
    */
      // pair使用volatile保证可见性
    private volatile Pair<V> pair;
    
        public boolean compareAndSet(V   expectedReference,  // 更新前的值
                                 V   newReference,    // 更新后的值
                                 int expectedStamp,   // 预期的版本号
                                 int newStamp) {        //更新后的版本号
           // 获取当前元素值 + 版本号  
         Pair<V> current = pair;
        return
            expectedReference == current.reference &&   // 比较引用
            expectedStamp == current.stamp &&        // 比较版本号
            ((newReference == current.reference &&    // 新引用等于旧引用
              newStamp == current.stamp) ||          // 新版本号等于旧版本号
             // 创建新的pair对象并CAS更新
             casPair(current, Pair.of(newReference, newStamp))); 
    }
}

compareAndSet方法执行结果:

  • 如果元素值版本号没有发生变化,并且和新的也相同,说明已经CAS更新过了返回true
  • 如果元素值版本号没有发生变化,和新的不同,就创建一个新的Pair并初始化为新值和版本号
  • 其他返回false

2.2 只能保证一个共享变量的原子操作

当对多个共享变量执行CAS操作时,就无法保证操作的原子性,比如对两个及以上变量的操作,对代码块的操作。

解决方案

Java1.5JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象中进行原子操作。

对于代码块可以选择使用 synchronizedLock 加锁。

2.3循环时间长开销大

CAS一般会使用循环操作不断重试(比如自旋锁),如果时间长会增大CPU资源的开销。

优化方案

可以设定自旋的次数,默认10次。JVM提供自适应自旋锁,自动调整CAS循环次数,可以根据前一次相同锁CAS执行的情况,判断CAS次数或者不使用CAS

你可能感兴趣的:(JUC,CAS,Java,JUC)