zoukankan      html  css  js  c++  java
  • 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)));
        }
  • 相关阅读:
    PAT B1027 打印沙漏 (20 分)
    PAT B1025 反转链表 (25 分)
    PAT B1022 D进制的A+B (20 分)
    PAT B1018 锤子剪刀布 (20 分)
    PAT B1017 A除以B (20 分)
    PAT B1015 德才论 (25 分)
    PAT B1013 数素数 (20 分)
    PAT B1010 一元多项式求导 (25 分)
    HDU 1405 The Last Practice
    HDU 1165 Eddy's research II
  • 原文地址:https://www.cnblogs.com/dpains/p/7375186.html
Copyright © 2011-2022 走看看