CAS详解

前言:只知道CAS是比较修改貌似在面试中还是不够,面试还需要知道CAS的缺陷,CAS的底层实现,这里以AtomicInteger为例进行分析,CAS的是干什么的这种不再赘述。

参考:

https://www.cnblogs.com/renqiqiang/articles/10129354.html

目录

第一章 AtomicInteger中涉及到CAS的函数

第二章 分析unsafe的几个方法

第三章 CAS的缺陷

3.1 ABA问题

3.2 多次自旋导致效率下降

3.3 多变量共享一致性问题


第一章 AtomicInteger中涉及到CAS的函数

经过对AtomicInteger源代码的查看,发现用到CAS主要是下面的函数,观察下面的函数,发现主要是用到了这个几个unsafe的函数,unsafe是封装了原子操作的JNI,使用unsafe能把操作原子化,原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换。

unsafe.getAndSetInt

unsafe.compareAndSwapInt

unsafe.getAndAddInt

 

public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }


    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    public final boolean weakCompareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }

    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }


    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

    public final int decrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
    }

    public final int addAndGet(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;

 

第二章 分析unsafe的几个方法

观察getAndSetInt、getAndAddInt的源码,发现它们都调用了compareAndSwapInt函数,那么可以看到传入了四个参数,

第一个参数代表要修改字段所在的对象,如我们其实传入了this,this也就代表AtomicInteger。

第二个参数代表valueOffset代表要修改字段相对于我们的对象在内存中的偏移量,如我们要修改AtomicInteger中的value,传入的就是value的偏移量,字段的偏移量在AtomicInteger的静态代码块(如下)中计算出(感觉这也间接说明静态字段在内存中的位置不会变?)

static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

第三个参数代表修改前的值,即我们期望的修改前的值。

第四个参数代表修改后的值,即我们期望的修改后的值。

//AtomicInteger.class
public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }

//Unsafe.class
public final int getAndSetInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var4));

        return var5;
    }

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

 

第三章 CAS的缺陷

参考:https://www.cnblogs.com/renqiqiang/articles/10129354.html

3.1 ABA问题

问题描述:线程t1将它的值从A变为B,再从B变为A。同时有线程t2要将值从A变为C。但CAS检查的时候会发现没有改变,但是实质上它已经发生了改变 。可能会造成数据的缺失。

解决方法:CAS还是类似于乐观锁,同数据乐观锁的方式给它加一个版本号或者时间戳,如AtomicStampedReference

3.2 多次自旋导致效率下降

问题描述:多个线程争夺同一个资源时,如果自旋一直不成功,将会一直占用CPU。

解决方法:破坏掉for死循环,当超过一定时间或者一定次数时,return退出。JDK8新增的LongAddr,和ConcurrentHashMap类似的方法。当多个线程竞争时,将粒度变小,将一个变量拆分为多个变量,达到多个线程访问多个资源的效果,最后再调用sum把它合起来。

虽然base和cells都是volatile修饰的,但感觉这个sum操作没有加锁,可能sum的结果不是那么精确。

3.3 多变量共享一致性问题

我们之前都是对一个变量使用循环CAS,如果对多个变量操作,1. 可以加锁来解决。2 .封装成对象类解决。

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值