• CAS(compare and swa)中的ABA问题及解决


    CAS(compare and swap)

    CAS是(compare and swap)的缩写,字面意思是比较交换。CAS锁通常也是实现乐观锁的一种机制,首先会给它一个期望值,用期望值与老值做比较,如果相等就用新传入的值进行修改。但是CAS通常有一个ABA问题,就是你把新值与老值做比较的时候,可能有其他线程已经修改过这个值了,只是后来最后值又被修改了回来,通常解决办法是用原子包装类的戳记引用的版本号机制,修改一次版本号也会发生自增,最后修改值的时候就会有期望值和期望版本号都得符合,不然修改失败。

    ABA问题的复现及解决

    package com.bilibili.juc.cas;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    /**
     * CAS是CompareAndSwap的简称 ,期望值与老值比对,如果一致把新值覆盖,但是会有一个问题,就是别的线程把值修改后再次修改为了原来的值,所以就加入版本号机制,
     * 常用来实现乐观锁
     * AtomicStampedReference :戳记引用, 在执行 CAS 操作时,不仅比较引用的值是否相同,还会比较一个标记值(Stamp)。只有当引用值和标记值都相同时,
     * 才会执行 CAS 操作。这样可以避免 ABA 问题,因为即使引用的值在过程中变化了,但如果标记值也变化了,CAS 操作就不会成功。
     */
    public class ABADemo {
        static AtomicInteger atomicInteger = new AtomicInteger(100);
        static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100, 1);
    
        public static void main(String[] args) {
            new Thread(() -> {
                int stamp = stampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号:" + stamp);
    
                //暂停500毫秒,保证后面的t4线程初始化拿到的版本号和我一样
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                stampedReference.compareAndSet(100, 101, stampedReference.getStamp(), stampedReference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + "\t" + "2次流水号:" + stampedReference.getStamp());
    
                stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + "\t" + "3次流水号:" + stampedReference.getStamp());
    
            }, "t3").start();
    
            new Thread(() -> {
                int stamp = stampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号:" + stamp);
    
                //暂停1秒钟线程,等待上面的t3线程,发生了ABA问题
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                boolean b = stampedReference.compareAndSet(100, 2022, stamp, stamp + 1);
    
                System.out.println("由于戳记标识被t3修改了,所以修改结果为:" + b + "值为:" + stampedReference.getReference() + ";戳记版本标识为:" + stampedReference.getStamp());
    
            }, "t4").start();
    
        }
    
        /**
         * 下面就是复现ABA问题
         */
        private static void abaHappen() {
            new Thread(() -> {
                atomicInteger.compareAndSet(100, 101);
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicInteger.compareAndSet(101, 100);
            }, "t1").start();
    
            new Thread(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(atomicInteger.compareAndSet(100, 2022) + "\t" + atomicInteger.get());
            }, "t2").start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
  • 相关阅读:
    自然语言处理 (NLP) 概述
    Nacos Config--服务配置
    PreScan快速入门到精通第二十五讲拖车和挂车模型
    JSP企业内部交流系统myeclipse开发mysql数据库bs框架java编程jdbc
    我在滴滴做开源
    用HTTP核心模块配置一个静态Web服务器
    “那天开始,我拒绝了油盐糖”
    Open Interpreter,一个让ChatGPT入驻你的电脑并获得联网能力成为贾维斯!
    基于Springboot+Vue+Element实现汽车租赁系统
    计算机组成原理——数据的表示和运算②
  • 原文地址:https://blog.csdn.net/qq_45925197/article/details/132882161