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)));
        }
  • 相关阅读:
    第二个冲刺
    实验四主存空间的分配和回收
    Sprint总结
    Sprint回顾
    Scrum项目5.0
    hibernate jpa 注解 @Temporal(TemporalType.DATE) 格式化时间日期,页面直接得到格式化类型的值
    获得HttpServletResponse及其他对象
    UUID.randomUUID()方法介绍
    psp个人软件过程需求文档
    电子产品自动搜索比价系统设计与实现 项目愿景与范围
  • 原文地址:https://www.cnblogs.com/dpains/p/7375186.html
Copyright © 2011-2022 走看看