2_尚硅谷面试 第二季 - CAS

CAS
是什么
比较并交换,预期值和实际值作比较,当相等的时间才去更新实际值
CAS的全程为Compare-And-Swap,他是一条CPU并发原语
他的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的

CAS并发原语体现在java语言中及时sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,
JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现原子操作。再次强调
由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的
一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也就是说CAS是一条CPU的原子
指令,不会造成所谓的数据不一致问题
/**
 * CAS
 */
public class Cas_2 {

    public static void main(String[] args) {

        AtomicInteger atomicInteger = new AtomicInteger(5);

        System.out.println("结果:" +atomicInteger.compareAndSet(5, 2022)+" 值为:"  + atomicInteger.get());// 结果:true 值为:2022

        System.out.println("结果:" +atomicInteger.compareAndSet(5, 2023)+" 值为:"  + atomicInteger.get()); //    结果:false 值为:2022

        /**
         * 第一次 做更改时,会去判断预期值 和 实际值是否相等,预期值和实际值都为 5 ,将实际值更改为 2022
         * 第一次 拿着 预期值(5) 去和 实际值 (2022) 做对比,发现不相等,则不更新
         */

    }
}
CAS底层原理
	atomicInteger.compareAndSet()
	=public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    // this 当前对象 valueOffset 内存中的偏移地址 expect 期望值 update 更新值
    =》
    unsafe 类 中的 compareAndSwapInt 方法
     public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
1 unsafe
是CAS的核心类,由于java方法无法直接访问底层系统,需要通过本地native方法来访问,unsafe相当于一个后门,基于该类
可以直接操作特定内存的数据。unsafe 类存在于 sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为java中
CAS操作的执行依赖于unsafe类的方法
2 变量valueOffset
表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的
3变量value用volatile修饰,保证了多线程的内存可见性
CAS 源码的实现
    /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
	=public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2); //根据this对象 内存中的偏移地址 获取到原先的值
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); // this对象 内存中的偏移地址 原值 更新的值

        return var5;
    }
	
理解:
	1.使用do while 当进入方法便去设置值,如果设置成功返回TRUE,取反退出循环
	2.如果设置失败,再次获取主内存中的值,接着设置
CAS 缺点
1、循环时间长开销大
2、只能保证一个共享变量的原子操作 (unsafe.compareAndSwapInt(this, valueOffset, expect, update);)
3、有ABA 问题
ABA 问题
比如一个值为 1 ,此时A线程、B线程读取的值都是为1 ,A线程在B线程比较之前将值改为2,在B线程比较之前C线程又将 2 改为1
此时,B线程去进行CAS,比较结果,预期值和实际值一样,更新成功,这样肯定是不对的
ABA 产生的原因
CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差内会导致数据的变化
比如线程one从内存位置V中取出A,这时另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变为B,然后线程
two又将V位置的数据变成A,这时候one操作发现内存中仍是A,然后one操作成功
尽管线程one操作成功,但是不代表这个过程就是没有问题
AtomicReference

class User{
  private   String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class TestAtomicRef {
    public static void main(String[] args) {
        User z3 = new User("z3", 15);
        User l4 = new User("l4", 25);
        AtomicReference<User> ref = new AtomicReference<>();
        ref.set(z3);
        System.out.println(ref.compareAndSet(z3, l4)+"  "+ref.get());
    }
}

AtomicStampedReference结局ABA 问题
public class TestAtomicStampedReference {
   static AtomicReference<Integer> ref = new AtomicReference<>(1);
   static AtomicStampedReference<Integer> sref = new AtomicStampedReference(1,1);

    public static void main(String[] args) {

        System.out.println(" =========== ABA  问题 ===========");
        new Thread(() -> {
            System.out.println(ref.compareAndSet(1, 127)); //注意 Integer 类型 127 以上 地址不同 具体百度 
		    /**
		     * Returns an {@code Integer} instance representing the specified
		     * {@code int} value.  If a new {@code Integer} instance is not
		     * required, this method should generally be used in preference to
		     * the constructor {@link #Integer(int)}, as this method is likely
		     * to yield significantly better space and time performance by
		     * caching frequently requested values.
		     *
		     * This method will always cache values in the range -128 to 127,
		     * inclusive, and may cache other values outside of this range.
		     *
		     * @param  i an {@code int} value.
		     * @return an {@code Integer} instance representing {@code i}.
		     * @since  1.5
		     */
		   /* public static Integer valueOf(int i) {
		        if (i >= IntegerCache.low && i <= IntegerCache.high)
		            return IntegerCache.cache[i + (-IntegerCache.low)];
		        return new Integer(i);
		    }*/

            System.out.println(ref.compareAndSet(127, 1));


        }, "A").start();

        new Thread(() -> {
            try {  TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {}
            System.out.println(Thread.currentThread().getName() + "  结果:" + ref.compareAndSet(1, 2022) + "值: " + ref.get());
        }, "B").start();
        try {  TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {}
        System.out.println(" =========== ABA  问题 解决 ===========");

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() +  " 初始版本 =》 "+ sref.getStamp());
            try {  TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) {} // 让 D 线程读取到 第一版数据
            System.out.println(Thread.currentThread().getName() + " 结果 " + sref.compareAndSet(1, 127, sref.getStamp(), sref.getStamp() + 1));
            System.out.println(Thread.currentThread().getName() + " 第一次修改之后的版本 : " + sref.getStamp());

            System.out.println(Thread.currentThread().getName() + " 结果 " + sref.compareAndSet(127, 1, sref.getStamp(), sref.getStamp() + 1));
            System.out.println(Thread.currentThread().getName() + " 第二次修改之后的版本 : " + sref.getStamp());


        }, "C").start();

        new Thread(() -> {
            int stamp = sref.getStamp(); //注意这个变量
            System.out.println(Thread.currentThread().getName() + " 初始版本 =》 "+ stamp);
            try {  TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) {} // 让C线程 将数据改为原先值
            System.out.println(Thread.currentThread().getName() + " 结果 " + sref.compareAndSet(1, 127, stamp, stamp + 1));
        }, "D").start();
    }


}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值