• java面试-CAS底层原理


    一、CAS是什么?

    比较并交换,它是一条CPU并发原语判断内存某个位置的值是否为预期值,如果是更改为新值,这个过程是原子的。

    原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题

    public class CASDemo {
    
        public static void main(String[] args) {
            //主物理内存的值默认是5
            AtomicInteger atomicInteger = new AtomicInteger(5);
            //如果线程的期望值与物理内存的真实值一样,就修改为更新值。
            System.out.println(atomicInteger.compareAndSet(5,2019)+" current data:"+atomicInteger.get()); //t1线程的工作内存 变量的副本拷贝
            System.out.println(atomicInteger.compareAndSet(5,1024)+" current data:"+atomicInteger.get()); //t2线程的工作内存 变量的副本拷贝
        } 
    }

    二、说说CAS底层原理?谈谈你对UnSafe的理解

    要点:Unsafe类(存在rt.jar中)+CAS自旋锁

    • AtomicInteger的源码
    public class AtomicInteger extends Number implements java.io.Serializable {
        private static final long serialVersionUID = 6214790243416807050L;
    
        // setup to use Unsafe.compareAndSwapInt for updates
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        static {
            try {
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
    
        private volatile int value;
    }

    1、Unsafe类

    是CAS的核心类,由于java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据

    Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作执行依赖于Unsafe类

    Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务  

    2、变量valueOffset,表示该变量在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

    //this:当前对象
    //valueOffset:内存偏移量 
     public final int getAndIncrement() {
            return unsafe.getAndAddInt(this, valueOffset, 1);
     }
    //var1 AtomicInteger对象本身
    //var2 该对象值的引用地址
    //var4 需要变动的值
    //var5 用var1 var2找出主内存中真实的值
    //用该对象当前值与var5比较,如果相同,更新var5+var4返回true,如果不同,继续取值比较,直到更新完成
    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;
    }  

    总结:getAndIncrement()底层调用unsafe类方法,传入三个参数,unsafe.getAndAddInt() 底层使用CAS思想,如果比较成功加1,如果比较失败重新获得,再比较一次,直至成功。

    3、变量value用volatile修饰,保证了多线程之间的内存可见性。

    三、CAS缺点:

    • 循环时间长开销大(如果CAS失败,会一直尝试)
    • 只能保证一个共享变量的原子操作。(对多个共享变量操作时,循环CAS无法保证操作的原子性,只能用加锁来保证)
    • 存在ABA问题

    四、原子类AtomicInteger类ABA问题及解决方案

    1、ABA问题是怎么产生的?

    当第一个线程执行CAS(V,E,U)操作,在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值,这样我们就无法正确判断这个变量是否已被修改过。

    2、ABA问题的解决方案:

    AtomicStampedReference:是一个带有时间戳的对象引用,在每次修改后,不仅会设置新值还会记录更改的时间。
    AtomicMarkableReference:维护的是一个boolean值的标识,这种方式并不能完全防止ABA问题的发生,只能减少ABA发生的概率。
    public class ABADemo {
        static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
        static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
    
    
        public static void main(String[] args) throws InterruptedException {
            System.out.println("========ABA问题的产生=========");
            new Thread(() -> {
                atomicReference.compareAndSet(100, 101);
                atomicReference.compareAndSet(101, 100);
    
            }, "t1").start();
    
            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(atomicReference.compareAndSet(100, 2019) + " " + atomicReference.get());
            }, "t2").start();
            TimeUnit.SECONDS.sleep(2);
    
            System.out.println("========ABA问题的解决=========");
    
            new Thread(() -> {
                int stamp = atomicStampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + "线程第1次版本号:" + stamp);
                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() + "线程第2次版本号:" + atomicStampedReference.getStamp());
                atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + "线程第3次版本号:" + atomicStampedReference.getStamp());
    
            }, "t3").start();
    
            new Thread(() -> {
                int stamp = atomicStampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + "线程第1次版本号:" + 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() + "修改成功否:" + result + "  当前最新版本号:" + atomicStampedReference.getStamp());
                System.out.println(Thread.currentThread().getName() + "当前最新值:" + atomicStampedReference.getReference());
    
            }, "t4").start();
        }
    }

    五、原子更新引用

    public class AtomicReferenceDemo {
    
        public static void main(String[] args) {
            AtomicReference<User> atomicReference = new AtomicReference<>();
    
            User user = new User("monster", 18);
            User updateUser = new User("jack", 25);
    
            atomicReference.set(user);
            atomicReference.compareAndSet(user, updateUser);
    
            System.out.println(atomicReference.get());
        }
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    class User {
        private String name;
        private int age;
    }  
      没有英汉互译结果
      请尝试网页搜索

  • 相关阅读:
    請問各位大大,我要將listview顯示的縮圖加入到listview2,請問該如何做呢
    一个可设置窗口透明属性的控件,可让窗口透明、半透明
    laravel he stream or file "..laravel-2019-02-14.log" could not be opened: failed to open stream: Permission denied
    每日学习-20190721
    linux centos无法删除网站根目录下的.user.ini解决办法
    laravel在使用Composer安装插件时要求输入授权用户名密码解决办法
    Centos7 日志查看工具
    Centos7 Putty SSH密钥登录
    阿里云Centos7用putty ssh链接掉线
    阿里云 centos 无法执行moodle cron
  • 原文地址:https://www.cnblogs.com/wjh123/p/11100612.html
Copyright © 2020-2023  润新知