zoukankan      html  css  js  c++  java
  • 理解CAS

    理解CAS

    public class CASDemo {
    
        //AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
    
        // 正常在业务操作,这里面比较的都是一个个对象
        static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
    
        // CAS  compareAndSet : 比较并交换!
        public static void main(String[] args) {
    
            new Thread(()->{
                int stamp = atomicStampedReference.getStamp(); // 获得版本号
                System.out.println("a1=>"+stamp);
    
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                Lock lock = new ReentrantLock(true);
    
                atomicStampedReference.compareAndSet(1, 2,
                        atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
    
                System.out.println("a2=>"+atomicStampedReference.getStamp());
    
    
                System.out.println(atomicStampedReference.compareAndSet(2, 1,
                        atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
    
                System.out.println("a3=>"+atomicStampedReference.getStamp());
    
            },"a").start();
    
    
            // 乐观锁的原理相同!
            new Thread(()->{
                int stamp = atomicStampedReference.getStamp(); // 获得版本号
                System.out.println("b1=>"+stamp);
    
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println(atomicStampedReference.compareAndSet(1, 6,
                        stamp, stamp + 1));
    
                System.out.println("b2=>"+atomicStampedReference.getStamp());
    
            },"b").start();
    
        }
    }
    

    Unsafe类

    image-20200804234433638

    image-20200804234456118

    CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就 一直循环!

    缺点:
    1、 循环会耗时
    2、一次性只能保证一个共享变量的原子性
    3、ABA问题

    ABA问题

    ABA问题是CAS机制中出现的一个问题,他的描述是这样的。我们直接画一张图来演示,

    img

    什么意思呢?就是说一个线程把数据A变为了B,然后又重新变成了A。此时另外一个线程读取的时候,发现A没有变化,就误以为是原来的那个A。这就是有名的ABA问题。ABA问题会带来什么后果呢?我们举个例子。

    一个小偷,把别人家的钱偷了之后又还了回来,还是原来的钱吗,你老婆出轨之后又回来,还是原来的老婆吗?ABA问题也一样,如果不好好解决就会带来大量的问题。最常见的就是资金问题,也就是别人如果挪用了你的钱,在你发现之前又还了回来。但是别人却已经触犯了法律。

    如何去解决这个ABA问题呢,就是使用AtomicStampedReference。

    AtomicStampedReference

    AtomicStampReference在cas的基础上增加了一个标记stamp,使用这个标记可以用来觉察数据是否发生变化,给数据带上了一种实效性的检验。它有以下几个参数:

    //参数代表的含义分别是 期望值,写入的新值,期望标记,新标记值
    public boolean compareAndSet(V expected,V newReference,int expectedStamp,int newStamp);
    
    public V getRerference();
    
    public int getStamp();
    
    public void set(V newReference,int newStamp);
    

    使用示例

    public class AtomicStampReferenceDemo {

    static AtomicStampedReference<Integer>  money =new AtomicStampedReference<Integer>(19,0);
     
    public static void main(String[] args) {
     
        for (int i = 0; i < 3; i++) {
     
            int stamp = money.getStamp();
     
            System.out.println("stamp的值是"+stamp);
     
            new Thread(){         //充值线程
     
                @Override
                public void run() {
     
                        while (true){
     
                            Integer account = money.getReference();
     
                            if (account<20){
     
                                if (money.compareAndSet(account,account+20,stamp,stamp+1)){
     
                                    System.out.println("余额小于20元,充值成功,目前余额:"+money.getReference()+"元");
                                    break;
                                }
                            }else {
     
                                System.out.println("余额大于20元,无需充值");
                            }
                        }
                    }
                }.start();
            }
    
            new Thread(){
     
                @Override
                public void run() {    //消费线程
     
                    for (int j = 0; j < 100; j++) {
     
                        while (true){
     
                            int timeStamp = money.getStamp();//1
     
                            int currentMoney =money.getReference();//39
     
                            if (currentMoney>10){
                                System.out.println("当前账户余额大于10元");
                                if (money.compareAndSet(currentMoney,currentMoney-10,timeStamp,timeStamp+1)){
     
                                    System.out.println("消费者成功消费10元,余额"+money.getReference());
     
                                    break;
                                }
                            }else {
                                System.out.println("没有足够的金额");
     
                                break;
                            }
                            try {
                                Thread.sleep(1000);
                            }catch (Exception ex){
                                ex.printStackTrace();
                                break;
                            }
     
                        }
     
                    }
                }
            }.start();
     
        }
    }
    

    这样实现了线程去充值和消费,通过stamp这个标记属性来记录cas每次设置值的操作,而下一次再cas操作时,由于期望的stamp与现有的stamp不一样,因此就会设值失败,从而杜绝了ABA问题的复现。

    ABA问题参考:https://www.cnblogs.com/wyq178/p/8965615.html

    视频参考https://www.bilibili.com/video/BV1B7411L7tE
    上一篇:彻底玩转单例模式
    下一篇:原子引用

  • 相关阅读:
    异步无刷新上传文件而且上传文件能够带上參数
    利用BADI WORKORDER_INFOSYSTEM在COOIS中加入自己定义列办事处
    Printf可变參数使用
    评大北农今日复牌公告
    iOS Sprite Kit教程之申请和下载证书
    UVa 12587 Reduce the Maintenance Cost(Tarjan + 二分 + DFS)
    Python: scikit-image Blob detection
    linux命令ps aux|grep xxx详解
    复制和重命名表格--修改表注释
    md5 破解网站
  • 原文地址:https://www.cnblogs.com/junlinsky/p/13443381.html
Copyright © 2011-2022 走看看