编程语言
首页 > 编程语言> > JAVA并发编程(十三)之CAS

JAVA并发编程(十三)之CAS

作者:互联网

一、CAS概念

CAS(compare and swap):比较并交换,CAS操作包含三个操作数,内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。同时CPU的一个原语操作,在intel的CPU中,使用cmpxchg指令。在JAVA中就是通过JNI对该原语的调用实现CAS。sun.misc.Unsafe 类中

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

原语:内核或微核提供核外调用的过程或函数称为原语(primitive);原语是一段用机器指令编写的完成特定功能的程序,在执行过程中不允许中断。
CPU中CAS的实现原理:如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg);如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。
intel的手册对lock前缀的说明如下:

  1. 确保对内存的读-改-写操作原子执行。
  2. 禁止该指令与之前和之后的读和写指令重排序。
  3. 把写缓冲区中的所有数据刷新到内存中,保证可见性。

二、CAS的缺点

循环占用CPU资源问题:由于CAS本身不会使线程进入阻塞,他是通过循环不断的比较,在高并发场景下,如果循环多次依旧无法更新,会给CPU带来很大的计算开销

ABA问题:在并发环境中,线程A先从内存中取出数据,一段时间后;再进行CAS时虽然比较成功,但是与当前值对比的内存中的值已经不是当初的哪个值了。
例如:小牛取款,由于机器不太好使,多点了几次取款操作。后台threadA和threadB同时读取到的值都是100,此时threadA阻塞,threadB操作成功(100->50)。正好牛妈打款50元给小牛(50->100),threadC执行成功,之后threadA运行了,又改为(100->50)。如此玩法就会发现小牛少了50大洋(正确:100-50+50=100)
如何解决aba问题:
对内存中的值加个版本号,在比较的时候除了比较值还的比较版本号;例如java:AtomicStampedReferenc

三、AtomicStampedReferenc 源码

数据结构:

private static class Pair<T> {
    final T reference;//存放设置的数据 
    final int stamp;//存放设置数据的版本号
    private Pair(T reference, int stamp) {
        this.reference = reference;
        this.stamp = stamp;
    }
    //构造 Pair对象
    static <T> Pair<T> of(T reference, int stamp) {
        return new Pair<T>(reference, stamp);
    }
}

构造函数:

//AtomicStampedReference 构造函数 
public AtomicStampedReference(V initialRef, int initialStamp) {
    pair = Pair.of(initialRef, initialStamp);
}

比较置换:compareAndSet

// expectedReference:预期对象  newReference:新的对象 
// expectedStamp:预期对象的版本号  newStamp:预期对象的版本号
public boolean compareAndSet(V expectedReference, V newReference,int expectedStamp,int newStamp) {
    Pair<V> current = pair;//当前Pari对象
    return
        expectedReference == current.reference //预期对象与当前对象是否相等
        && expectedStamp == current.stamp //预期对象的版本号与当前对象的版本号是否相等
        //新对象与当前对象相等,并且他们的版本号也相等 或者 执行CSA成功
        &&((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp)));
}

 

标签:JAVA,reference,CAS,编程,版本号,int,Pair,stamp
来源: https://blog.csdn.net/jalja365/article/details/111559554