理解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
上一篇:彻底玩转单例模式
下一篇:原子引用

posted @ 2020-08-05 22:55  Maple_XL  阅读(175)  评论(0编辑  收藏  举报