CAS ABA问题

接触并发编程少不了CAS,这里不讲CAS,在另一篇文章里面有写CAS,这里只关注CAS的ABA问题。

什么叫CAS的ABA问题?

因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。

比如线程A操作值a,将值修改为b,这时线程B也拿到了a的值,将a改为b,再改为a,这时线程A比对值的时候,发送值还是和expect的一样,就会继续操作。如下面代码,T2的CAS操作是可以成功的。

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);
                atomicInt.compareAndSet(101, 100);
            }
        });

        Thread intT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                }
                boolean c3 = atomicInt.compareAndSet(100, 101);
                System.out.println(c3); // true
            }
        });

        intT1.start();
        intT2.start();
        intT1.join();
        intT2.join();
}

ABA问题导致的原因,是CAS过程中只简单进行了“值”的校验,再有些情况下,“值”相同不会引入错误的业务逻辑(例如库存),有些情况下,“值”虽然相同,却已经不是原来的数据了。

优化方向:CAS不能只比对“值”,还必须确保的是原来的数据,才能修改成功。

常见实践:“版本号”的比对,一个数据一个版本,版本变化,即使值相同,也不应该修改成功。

java中的AtomicStampedReference就是使用的版本号来进行改进的。核心测试代码如下,这时候T2的CAS操作为false。同时打印了每个CAS操作时的stamp,cas1:0,cas2:1,cas3:0,这样在最终比对的时候,就不是简单的值比对了,还会加入版本号的比对。

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

Thread refT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                }
                System.out.println("cas1---"+atomicStampedRef.getStamp());
                atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);

                System.out.println("cas2---"+atomicStampedRef.getStamp());
                atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
            }
        });

        Thread refT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = atomicStampedRef.getStamp();
                System.out.println("cas3---"+stamp);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                }
                boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
                System.out.println(c3); // false
            }
        });

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

拜读一下源码,主要看下数据结构和CAS方法:

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);
        }
    }

public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,  //增加了stamp的参数
                                 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)));
    }
时间: 2024-10-29 10:02:20

CAS ABA问题的相关文章

CAS / ABA

CAS / ABA 标签(空格分隔): 操作系统 1. CAS 解决 Volatile 不保证原子性的问题 /** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } public final int ge

Java CAS ABA问题发生的场景分析

提到了CAS操作存在问题,就是在CAS之前A变成B又变回A,CAS还是能够设置成功的,什么场景下会出现这个问题呢?查了一些资料,发现在下面的两种情况下会出现ABA问题. 1.A最开始的内存地址是X,然后失效了,有分配了B,恰好内存地址是X,这时候通过CAS操作,却设置成功了 这种情况在带有GC的语言中,这种情况是不可能发生的,为什么呢?拿JAVA举例,在执行CAS操作时,A,B对象肯定生命周期内,GC不可能将其释放,那么A指向的内存是不会被释放的,B也就不可能分配到与A相同的内存地址,CAS失败

Java并发编程原理与实战四十三:CAS ---- ABA问题

CAS(Compare And Swap)导致的ABA问题 问题描述 多线程情况下,每个线程使用CAS操作欲将数据A修改成B,当然我们只希望只有一个线程能够正确的修改数据,并且只修改一次.当并发的时候,其中一个线程已经将A成功的改成了B,但是在线程并发调度过程中尚未被调度,在这个期间,另外一个线程(不在并发中的请求线程)将B又修改成了A,那么原来并发中的线程又可以通过CAS操作将A改成B 测试用例: public class AbaPro { private static final Rando

volatile | CAS| ABA

JMM JMM(Java内存模型Java Memory Model)是一种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段.静态字段和构成数组对象的元素)的访问方式. JMM关于同步的规定: ①线程解锁前,必须把共享变量的值刷新回主内存: ②线程解锁前,必须读取主内存的最新值到自己的工作内存: ③加锁解锁是同一把锁: ①把变量读到各个线程的工作内存中:②线程运算完之后把变量的值改好,然后把它写回主内存:③可见性--让其他线程马上知道这个值: 各个线

CAS -- ABA问题的解决方案

我们现在来说什么是ABA问题.假设内存中有一个值为A的变量,存储在地址V中. 此时有三个线程想使用CAS的方式更新这个变量的值,每个线程的执行时间有略微偏差.线程1和线程2已经获取当前值,线程3还未获取当前值. 接下来,线程1先一步执行成功,把当前值成功从A更新为B:同时线程2因为某种原因被阻塞住,没有做更新操作:线程3在线程1更新之后,获取了当前值B. 在之后,线程2仍然处于阻塞状态,线程3继续执行,成功把当前值从B更新成了A. 最后,线程2终于恢复了运行状态,由于阻塞之前已经获得了“当前值A

J.U.C atomic 数组,字段原子操作

这里看一下原子数组操作和一些其他的原子操作. AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray的API类似,选择代表性的AtomicIntegerArray来描述这些问题. int get(int i) //获得当前位置的值 void set(int i, int newValue) //设置给定位置的值 void lazySet(int i, int newValue) int getAndSet(int i, int newVal

Java_锁

乐观锁 悲观锁 独占锁 共享锁 阻塞算法 非阻塞算法 自旋锁 AQS CLH队列锁 MCS队列锁 Ticket队列锁 SMP NUMA CAS ABA问题 原子变量:AtomicReference AtomicInteger AtomicReferenceFieldUpdater

java并发编程之原子操作

先来看一段简单的代码,稍微有点并发知识的都可以知道打印出结果必然是一个小于20000的值 package com.example.test.cas; import java.io.IOException; /** * @author hehang on 2019-10-09 * @description */ public class LockDemo { private volatile int i; public void add(){ i++; } public static void m

JUC 中的 Atomic 原子类总结

1 Atomic 原子类介绍 Atomic 翻译成中文是原子的意思.在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的.在我们这里 Atomic 是指一个操作是不可中断的.即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰. 所以,所谓原子类说简单点就是具有原子/原子操作特征的类. 并发包 java.util.concurrent 的原子类都存放在java.util.concurrent.atomic下,如下图所示. 根据操作的数据类型,可以将JUC包中