CAS 的ABA 问题

CAS

CAS:Compare and Swap, 翻译成比较并交换。
java.util.concurrent包中借助CAS实现了区别于synchronized同步锁的一种乐观锁。
其原理是CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

ABA问题

    private static AtomicInteger atomicInt = new AtomicInteger(100);
    public static void main(String[] args) throws InterruptedException {
        Thread intT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                atomicInt.compareAndSet(100, 101);
                log("thread intT1:" + atomicInt.get());
                atomicInt.compareAndSet(101, 100);
                log("thread intT1:" + atomicInt.get());
            }
        });

        Thread intT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean c3 = atomicInt.compareAndSet(100, 101);
                log("thread intT2:" + atomicInt.get() + ",c3 is:" + c3);        //true
            }
        });

        intT1.start();
        intT2.start();

上面程序的打印结果如下:

thread intT1:101
thread intT1:100
thread intT2:101,c3 is:true

线程intT2获取到的变量值A,尽管和当前的实际值相同,但内存地址V中的变量已经经历了A->B->A的改变。实际应用中有可能会导致漫画:什么是CAS机制?(进阶篇)中所提到的提款问题。

解决方案

JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。如果当前引用 == 预期引用,并且当前标志等于预期标志,则以原子方式将该引用和该标志的值设置为给定的更新值。源码如下:

/**
 *expectedReference - 该引用的预期值
 *newReference - 该引用的新值
 *expectedStamp - 该标志的预期值
 *newStamp - 该标志的新值
 */
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) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

最佳实践

    private static AtomicStampedReference<Integer> atomicStampedRef =
            new AtomicStampedReference<Integer>(100, 0);

        Thread refT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicStampedRef.compareAndSet(100, 101,
                        atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
                log("thread refT1:" + atomicStampedRef.getReference());
                atomicStampedRef.compareAndSet(101, 100,
                        atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
                log("thread refT1:" + atomicStampedRef.getReference());
            }
        });

        Thread refT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = atomicStampedRef.getStamp();
                log("before sleep : stamp = " + stamp);    // stamp = 0
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log("after sleep : stamp = " + atomicStampedRef.getStamp());//stamp = 1
                boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
                log("thread refT2:" + atomicStampedRef.getReference() + ",c3 is " + c3);        //true
            }
        });

        refT1.start();
        refT2.start();
    }

    private static void log(String logString) {
        System.out.println(logString);
    }

输出结果如下:

before sleep : stamp = 0
thread refT1:101
thread refT1:100
after sleep : stamp = 2
thread refT2:100,c3 is false

可以看到refT2的值和expect是相同的,但是由于版本号发生了变化,所以更新失败。

作者:时光之刃51y
链接:https://www.jianshu.com/p/8de8c6a839e8
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

原文地址:https://www.cnblogs.com/daxiong225/p/12112796.html

时间: 2024-08-08 13:23:06

CAS 的ABA 问题的相关文章

Java高性能编程之CAS与ABA及解决方法

Java高性能编程之CAS与ABA及解决方法 前言 如果喜欢暗色调的界面或者想换换界面,可以看看我在个人博客发布的 Java高性能编程之CAS与ABA及解决方法. CAS概念 CAS,全称Compare And Swap,比较与交换. 属于硬件级别的同步原语,从处理器层面提供了内存操作的原子性. 从概念上,我们可以得出三点.第一,CAS的运作方式(通过比较与交换实现).第二,硬件层面支持,性能肯定不低(当然它也不是银弹).第三,提供原子性,那么它的功能肯定是确保原子性,从而确保线程安全. 实际使

CAS 和 ABA 问题

CAS简介CAS 全称是 compare and swap,是一种用于在多线程环境下实现同步功能的机制. CAS 它是一条CPU并发原语.操作包含三个操作数 -- 内存位置.预期数值和新值.CAS 的实现逻辑是将内存位置处的数值与预期数值想比较,若相等,则将内存位置处的值替换为新值.若不相等,则不做任何操作.这个过程是原子的. CAS并发原语体现在java语言中的sun.misc.Unsafe类中的各个方法.调用Unsafe类中的CAS方法,JVM会帮我们实现汇编指令.这是一种完全依赖硬件的功能

无锁编程:lock-free原理;CAS;ABA问题

转自:http://blog.csdn.net/kangroger/article/details/47867269 定义 无锁编程是指在不使用锁的情况下,在多线程环境下实现多变量的同步.即在没有线程阻塞的情况下实现同步.这样可以避免竞态.死锁等问题. 原理 CAS是指Compare-and-swap或Compare-and-Set CAS是一个原子操作,用于多线程环境下的同步.它比较内存中的内容和给定的值,只有当两者相同时(说明其未被修改),才会修改内存中的内容. 实现如下: int comp

Java CAS 和ABA问题

http://www.cnblogs.com/549294286/p/3766717.html 独占锁:是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁. 乐观锁:每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止. 一.CAS 操作 乐观锁用到的机制就是CAS,Compare and Swap. CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B.当且仅当预期值A和内存值V相同时,将内存值V修改为B,

聊聊高并发(十二)分析java.util.concurrent.atomic.AtomicStampedReference源码来看如何解决CAS的ABA问题

在聊聊高并发(十一)实现几种自旋锁(五)中使用了java.util.concurrent.atomic.AtomicStampedReference原子变量指向工作队列的队尾,为何使用AtomicStampedReference原子变量而不是使用AtomicReference是因为这个实现中等待队列的同一个节点具备不同的状态,而同一个节点会多次进出工作队列,这就有可能出现出现ABA问题. 熟悉并发编程的同学应该知道CAS操作存在ABA问题.我们先看下CAS操作. CAS(Compare and

CAS与ABA问题产生和解决

乐观锁和悲观锁 Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守.CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新. 性能对比: Synchronized关键字会让没有得到锁资源的线程进入blocked状态,而后在争夺到锁资源后恢复为runnable状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高. 尽管Java1.6为Synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重

CAS的ABA问题

一.CAS概念 1. 悲观锁:一个线程加锁后其他线程会挂起,直到持有锁的线程释放锁,悲观锁从最坏的情况考虑问题.独占锁是一种悲观锁,例如synchronized关键字加锁,例如可重入锁ReentrantLock 2. 乐观锁:不加锁,多个线程共同执行,如果发生了冲突,就去重试(do...while...),乐观锁从最好的情况考虑问题 3. 悲观锁和乐观锁哪个效率高,要看具体情况 4. CAS(Compare And Swap,比较并交换)算法是一种乐观锁 5. CAS有3个操作数:旧值A,新值B

CAS的ABA问题有什么危害?

CAS CAS(CompareAndSet)是保证并发安全性的一条CPU底层原子指令,它的功能是判断某个值是否为预期值,如果是的话,就改为新值,在CAS过程中不会被中断. compareAndSet 在JNI(Java Naive Interface)中实现,位于unsafe.cpp文件,关键的语句是 cmpxchg(x, addr, e),其中x指的是旧值,addr是要和oldValue一致的内存位置,而e是要变为的新值.执行该原子语句时,将oldValue和从addr取出的值进行比较,相等的

SpinLock 自旋锁, CAS操作(Compare &amp; Set) ABA Problem

SpinLock 自旋锁 spinlock 用于CPU同步, 它的实现是基于CPU锁定数据总线的指令. 当某个CPU锁住数据总线后, 它读一个内存单元(spinlock_t)来判断这个spinlock 是否已经被别的CPU锁住. 如果否, 它写进一个特定值, 表示锁定成功, 然后返回. 如果是, 它会重复以上操作直到成功, 或者spin次数超过一个设定值. 锁定数据总线的指令只能保证一个机器指令内, CPU独占数据总线. 单CPU当然能用spinlock, 但实现上无需锁定数据总线. spinl