CAS(比较并交换)

在上一篇文章我们说到,在多线程的情况下,i++无法保证原子性,会出现问题,所以引入了AtomicInteger原子类。
使用原子的方式更新基本类型,Atomic包提供了以下3个类。
AtomicBoolean:原子更新布尔类型。
AtomicInteger:原子更新整型。
AtomicLong:原子更新长整型。

我们先对AtomicInteger类相关使用做个简单的介绍:

    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger();
        System.out.println( atomicInteger.get());
        atomicInteger.set(3);
        System.out.println(atomicInteger.get());

        AtomicInteger atomicInteger2 = new AtomicInteger(9);
        System.out.println( atomicInteger2.get());
        atomicInteger2.set(4);
        System.out.println(atomicInteger2.get());
    }

运行程序:
在这里插入图片描述

我们发现当我们创建AtomicInteger对象时,如果不传值,则默认是0,也可以传入一个指定的值。
atomicInteger.get(); //获取当前值
atomicInteger.set(3); //设置当前值

常用自增、自减方法:

public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        //相当于i--,看方法名就知道,获取当前值并自减
        System.out.println(atomicInteger.getAndDecrement());
        System.out.println(atomicInteger.get());
        System.out.println("=================");
        //相当于--i,看方法名就知道,先自减再获取减1后的值
        System.out.println(atomicInteger.decrementAndGet());
        System.out.println(atomicInteger.get());
        System.out.println("=================");
        //相当于i++,返回的是旧值,看方法名就知道,先获取再自增
        System.out.println(atomicInteger.getAndIncrement());
        System.out.println(atomicInteger.get());
        System.out.println("=================");
        //相当于++i,返回的是旧值,看方法名就知道,先自增再获取
        System.out.println(atomicInteger.incrementAndGet());
        System.out.println(atomicInteger.get());

    }

运行结果:
在这里插入图片描述

CAS(Compare and Swap比较并交换)

CAS是一条CPU并发原语,它的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的。CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中的各个方法,调用UnSafe类中的CAS方法,JVM会帮我们实现CAS汇编指令,这是一种完成依赖于硬件的功能,通过它可以实现原子性操作。由于CAS是一种系统原语,原语属于系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中是不允许被中断的,也就是是CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
compareAndSet(int expect, int update)
当期望值和真实值一样,则更新为update值,返回true;
当期望值和真实值不一样,则更新失败,返回false,我们需要重新获取主内存中的值。

  public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);

        boolean flag1 = atomicInteger.compareAndSet(5, 10);
        System.out.println("flag1的值为"+flag1+",此时atomicInteger为:"+atomicInteger.get());

        boolean flag2 = atomicInteger.compareAndSet(5, 25);
        System.out.println("flag2的值为"+flag2+",此时atomicInteger为:"+atomicInteger.get());

    }

运行结果:
在这里插入图片描述
我们在上一篇文章我们使用atomicInteger.getAndIncrement()来解决了多线程下a++的问题,它是如何在不加锁的情况下保证运行正常的呢。
我们可以点进去看下底层调用的是getAndAddInt方法,每调用一次加1。
在这里插入图片描述
在这里插入图片描述

1.Unsafe
Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定的内存数据,Unsafe类存在sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为java中CAS操作的执行依赖于Unsafe类的方法,其方法由native修饰,也就是说Unsafe的方法都直接调用操作系统底层资源执行相应的任务。
2.变量valueOffset为内存偏移量,Unsafe就是根据内存偏移地址获取数据的。
3.变量value用volatile修饰,保证了多线程之间的内存可见性。
我们再点到Unsafe类的getAndAddInt方法中,源码如下:
在这里插入图片描述
var1为当前对象
var2为内存偏移量
var4为1
var5为获取当前对象var1和内存偏移量var2在内存中的值
我们看到有一个do while去比较更新,如果内存中值和当前值相同则新增1,不相同在接着比较。从而我们也可以知道CAS的缺点:如果CAS失败就会一直进行尝试,长时间不成功,可能会跟CPU带来很大的开销。

CAS会导致”ABA问题“

ABA问题:
线程1从内存位置V中取出A,这个时候线程2也内存中取出A,进行一些操作将值变成了B,然后线程2又将位置V的数据变成A,这个时候线程1进行CAS操作的社会发现内存中还是A,则操作成功,虽然线程1操作成功,但是不代表这个过程没有问题。
代码演示:

public class ABATest {
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(5);

    public static void main(String[] args) {
        new Thread(()->{
            atomicReference.compareAndSet(5,6);
            atomicReference.compareAndSet(6,5);
        },"线程1").start();

        new Thread(()->{
            //暂停1秒,保证线程1完成一次ABA
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(atomicReference.compareAndSet(5, 7)+"\t"+atomicReference.get());

        },"线程2").start();
    }
}

运行结果:
在这里插入图片描述

解决ABA问题:
引入类似乐观锁的版本号控制,除了比较预期值和内存位置的值,还要比较版本号是否正确。
atomic包就提供了AtomicStampedReference类来解决ABA问题,相较CAS引入了一个标志,在比较完预期值与内存地址值之后,再对预期标志和现有标志做比较,都通过才执行更新操作。

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

    public static void main(String[] args) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"第一次版本号:"+atomicStampedReference.getStamp());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(5,6,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"第二次版本号:"+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(6,5,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"第三次版本号:"+atomicStampedReference.getStamp());
        },"线程1").start();

        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"第一次版本号:"+stamp);
            //暂停3秒,保证线程1完成一次ABA
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            boolean b = atomicStampedReference.compareAndSet(5, 7, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"修改结果"+b+",当前版本号:"+atomicStampedReference.getStamp());
            System.out.println("最终结果值为"+atomicStampedReference.getReference());

        },"线程2").start();
    }
}

运行结果:
在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值