• 并发编程之CAS(二)


    更多Android架构进阶视频学习请点击:https://space.bilibili.com/474380680
    本篇文章将从以下几个内容来阐述CAS:

    • [CAS原理]
    • [CAS带来的ABA问题]

    一、什么是CAS?

    在计算机科学中,比较和交换(Conmpare And Swap)是用于实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。 这是作为单个原子操作完成的。 原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。 操作结果必须说明是否进行替换; 这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成(摘自维基本科)
    
    JAVA1.5开始引入了CAS,主要代码都放在JUC的atomic包下,如下图:
    
     
    19956127-d8fe2b2f5c06d91c.jpg
     

    二、CAS(Compare And Swap)导致的ABA问题

    问题描述

    多线程情况下,每个线程使用CAS操作欲将数据A修改成B,当然我们只希望只有一个线程能够正确的修改数据,并且只修改一次。当并发的时候,其中一个线程已经将A成功的改成了B,但是在线程并发调度过程中尚未被调度,在这个期间,另外一个线程(不在并发中的请求线程)将B又修改成了A,那么原来并发中的线程又可以通过CAS操作将A改成B

    测试用例:

    public class AbaPro {
    
        private static final Random RANDOM = new Random();
        private static final String B = "B";
        private static final String A = "A";
        public static final AtomicReference<String> ATOMIC_REFERENCE = new AtomicReference<>(A);
    
    
        public static void main(String[] args) throws InterruptedException {
            final CountDownLatch startLatch = new CountDownLatch(1);
    
            Thread[] threads = new Thread[20];
            for (int i=0; i < 20; i++){
                threads[i] = new Thread(){
                    @Override
                    public void run() {
                        String oldValue = ATOMIC_REFERENCE.get();
                        try {
                            startLatch.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        try {
                            Thread.sleep(RANDOM.nextInt()&500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        if (ATOMIC_REFERENCE.compareAndSet(oldValue, B )){
                            System.out.println(Thread.currentThread().getName()+ " 已经对原始值进行了修改,此时值为: "+ ATOMIC_REFERENCE.get());
                        }
                    }
                };
                threads[i].start();
            }
    
            startLatch.countDown();
            Thread.sleep(200);
    
            new Thread(){
    
                @Override
                public void run() {
                    try {
                        Thread.sleep(RANDOM.nextInt() & 200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    String oldVal = ATOMIC_REFERENCE.get();
                    while (!ATOMIC_REFERENCE.compareAndSet(ATOMIC_REFERENCE.get(), A));
                    System.out.println(Thread.currentThread().getName() +" 已经将值 "+oldVal+" 修改成原始值: A");
                }
    
            }.start();
        }
    
    }
    

    结果:

    Thread-12 已经对原始值进行了修改,此时值为: B
    Thread-20 已经将值 B 修改成原始值: A
    Thread-14 已经对原始值进行了修改,此时值为: B
    

    可以看到并发中的线程Thread-12已经成功的将A修改成B,其他线程Thread-20在某一时刻将B修改成A,而并发中的线程Thread-14又能再次成功的将A修改成B,虽然最终结果是B,但是中途经历了一次被修改的过程,在某些情况下是致使的

    解决方案

    java中提供了AtomicStampedReference来解决这个问题,它是基于版本或者是一种状态,在修改的过程中不仅对比值,也同时会对比版本号

    public class AabProResolve {
    
        private static final Random RANDOM = new Random();
        private static final String B = "B";
        private static final String A = "A";
    
        private static final AtomicStampedReference<String> ATOMIC_STAMPED_REFERENCE = new AtomicStampedReference<>(A,0);
    
        public static void main(String[] args) throws InterruptedException {
    
            final CountDownLatch startLatch = new CountDownLatch(1);
            Thread[] threads = new Thread[20];
    
            for (int i=0; i < 20; i++){
                threads[i] = new Thread(){
    
                    @Override
                    public void run() {
                        String oldValue = ATOMIC_STAMPED_REFERENCE.getReference();
                        int stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
    
                        try {
                            startLatch.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        try {
                            Thread.sleep(RANDOM.nextInt() & 500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (ATOMIC_STAMPED_REFERENCE.compareAndSet(oldValue, B, stamp, stamp+1)){
                            System.out.println(Thread.currentThread().getName()+ " 已经对原始值: "+oldValue+" 进行了修改,此时值为: "+ ATOMIC_STAMPED_REFERENCE.getReference());
                        }
                    }
                };
                threads[i].start();
            }
            Thread.sleep(200);
            startLatch.countDown();
    
            new Thread(){
                @Override
                public void run() {
    
                    try {
                        Thread.sleep(RANDOM.nextInt() & 200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    int stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
                    String oldVal = ATOMIC_STAMPED_REFERENCE.getReference();
                    while (!ATOMIC_STAMPED_REFERENCE.compareAndSet(
                            B,
                            A,stamp, stamp+1)){
                        stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
                    }
                    System.out.println(Thread.currentThread().getName() +" 已经将值 "+oldVal+" 修改成原始值: A");
    
    
                }
            }.start();
    
        }
    
    }
    

    结果:

    Thread-1 已经对原始值: A 进行了修改,此时值为: B
    Thread-20 已经将值 B 修改成原始值: A
    

    可以看到并发期间的线程只有Thread-1对A进行了修改,保证了只有一个线程对数据的修改,短暂的并发时间之后的其他线程Thread-20对其修改自然也就没有影响
    更多Android架构进阶视频学习请点击:[https://space.bilibili.com/474380680]
    参考:https://blog.csdn.net/u013887008/article/details/79784298
    https://www.cnblogs.com/javalyy/p/8882172.html

  • 相关阅读:
    c++ 输出 变量名 字符串(zz.is2120.BG57IV3)
    分页存储过程
    连接字符串
    动软 DBHeper 完全代码
    java 数据库连接字符串
    DOS命令行下常见的错误信息
    点击单元格选择整行,又可编辑单元格
    label里文字中的下划线
    Delphi程序中动态生成控件的方法及应用
    双击dbgrid排序的问题
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/11937176.html
Copyright © 2020-2023  润新知