CAS与ABA问题

目录

为什么要讲CAS

什么是CAS

CAS的应用

CAS的工作原理

CAS的优缺点

什么是ABA问题

如何解决ABA问题?


 

为什么要讲CAS

 

不明白为什么要讲CAS,你会记不住这个东西应该在什么时候使用。我们之前讲过,为了保证合理的操作JMM使用了Volatile关键字、但Volatile没法保证操作的原子性、为此引用了AtomicInteger之前讲到上面就戛然而止了,今天继续往下讲。

AtomicInteger之所以能保证原子性,不是因为设计师设计了一个Atomic的名字,而是因为Atomic底层调用了本地操作系统的unsafe方法、unsafe方法可以根据内存偏移量操作内存中的值、而实现操作内存偏移量的办法就是使用CAS原子性操作~~~

还以+1为例,atomicInteger.getAndIncrement()方法通过一个原子性操作实现了+1的效果,弥补了volatile的不足。

 

 

什么是CAS

 

CAS意为比较并交换我们知道工作内存中修改数据后会刷新到主存中,CAS机制是在刷新前确认一件事:主存中的内容是不是和最开始拷贝给工作内存的内容一样,这叫比较。如果一样,则把工作内存更新后的值赋给主存,这叫交换。

CAS方法返回的值是布尔型,交换成功返回true,否则返回false

我们先举个小例子。我们在volatile中为了保证了操作的原子性,当时引入了AtomicInteger类。Atomic类中更新值方法的底层原理就是CAS。我们new一个AtomicInteger,然后调用一下CAS方法,最后打印的结果发现就是比较并交换的逻辑

 

 

CAS的应用

 

1.Aotmic类型的类

2.ConcurrentHashMap

 

 

CAS的工作原理

 

点进AtomicInteger源码,发现里面有unsafe。unsafe来自于Rt.jar。那什么是unsafe呢?unsafe是java调用C++的native方法操作内存的标志。它可以通过操作valueOffset(内存偏移值)来定位变量在内存中的位置,直接在内存中操作变量,比如+1,这一操作是由原子性的。

为什么是原子的?因为CAS是CPU的操作原语,原语执行时不会被打乱顺序,即原子性的。

我们再点进getAndIncrement方法,发现它底层调用了getAndAddInt

进入getAndAddInt才发现,这里用了一个自旋锁。原来比较和交换的逻辑写在这里。

比较的是期望值和主存的值是否一致,如果一致更新工作线程的值,如果不一致取出主存的值。取出主存的值后作为期望值,再运算出工作线程的最新值,在自旋锁中又循环了一遍后发现期望值和主存值相等了,这回就可以更新了~

 

 

CAS的优缺点

 

优点:

1.直接操作内存,效率非常高。 

2.保证了操作的原子性

 

缺点:

1.如果CAS不是一下就成功,会无限循环,循环会耗费时间(自旋锁)

2.一次性只能保证一个共享变量的原子性

3.会出现ABA问题

 

 

什么是ABA问题

 

ABA问题出现的原因是CAS只比较工作内存一开始从主存中取出的值是否和更新前主存的值一致。只要数一样就执行“比较并交换”操作,但这样显然不合理。

假如现在模拟考完试学生自己交卷的场景,老师在教室外和主任唠嗑。第一个同学交卷后走了,第二个同学交卷时发现自己不会写的题上一个同学全写出来了,它把本来已经放在第一张卷子上面的自己的试卷,拿回去又去写。这时老师进来了,虽然讲台上还是只有一张卷子,但老师认为只有一个人来到讲台上交卷。这就不合理了。

下面我们使用AtomicReference来模拟ABA问题,AtomicReference功能和AtomicInteger类似,只不过一个是作用在引用类型上,另一个是作用在int类型上。

public class ABA {
    static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100);

    public static void main(String[] args) {
        new Thread(()->{
            atomicReference.compareAndSet(100,101);
            atomicReference.compareAndSet(101,100);
        },"t1").start();

        new Thread(()->{
            //暂停1s,为了让t2线程在t1线程后执行
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(100,2019)+"\t"+atomicReference.get());
        },"t2").start();
    }
}

得到的结果是true 2019。B线程根本不知道主存在这之间有过更改

 

 

如何解决ABA问题?

 

核心是利用乐观锁的思想,仍然是“比较并交换”,但比的不再是值而是“值+stemp。”

JUC给我们提供了一个基于乐观锁的原子引用类——AtomicStampedReference

示例如下

public class ABA {
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100,1);

    public static void main(String[] args) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+ "打印最开始的版本号" + atomicStampedReference.getStamp());
            try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) { e.printStackTrace();}
            atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+ "第一次更改后的版本号" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+ "第二次更改后的版本号" + atomicStampedReference.getStamp());
        },"t1").start();


        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+ "打印最开始的版本号" + stamp);
            try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) { e.printStackTrace();}
            boolean result = atomicStampedReference.compareAndSet(100,2019,stamp,stamp+1);
            System.out.println(Thread.currentThread().getName()+"\t是否修改成功?"+result+"\t最新版本号"+atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName()+"\t当前实际最新值"+atomicStampedReference.getReference());
        },"t2").start();
    }
}

 最后打印的结果:

由于线程t2更新前发现主存中的stemp已经变为3、不是期望的2,因此更新失败~

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值