zoukankan      html  css  js  c++  java
  • java基础----CAS

    一.什么是cas

      CAS的全称是Compare-And-Swap,他是一条CPU并发原语。

      java中的CAS,都是通过unsafe类实现的,其主要的操作是,当一个线程从主内存拿到一个变量到自己工作内存,并经过计算处理,准备写回主内存的时候,会首先比对当前主内存的变量指向的内存地址里面的值,与期望值(线程一开始拿变量时,变量对应的值)是否相等,如果相等,则表示没有其他线程对这个变量操作过,随后就将要更新的值写进主内存中。假如不相等,则表示有线程修改过这个变量,则会把主内存中变量的最新值拿回去,重新做一次计算操作,以此循环。

    二.cas的底层原理

    下面是java atomicInteger的代码:

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

    下面是unsafe.class中的代码(这个类是java的原生类,在jdk的rt.jar/sun/misc里面):

    //第一个参数var1为给定对象,var2为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,
    //var5表示期望值,var4表示要添加的数值
    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;
    }

           一般来说,java无法直接访问底层系统,需要通过本地native方法来访问,而unsafe相当于一个后门,基于该类可以直接操作特定的内存。

      可以看出,unsafe类可以直接操作指针,根据给定的对象和内存偏移量迅速地获取到变量值。在do...while中,首先根据对象和内存偏移量拿到一个值作为期望值,然后在while的条件语句中,再一次根据对象和内存偏移量获取变量的当前值,并与期望值作出对比,如果相等,则加上var4。假如不相等,则从新获得期望值,并循环。

    三.CAS的缺点

      最明显的一点,CAS有可能出现一个很长的循环,假如线程一直没有写成功,那他就会一直自旋,非常消耗CPU资源。而且它只能保证一个共享变量的操作,对于多变量操作,只能加锁。

      其次就是ABA问题,导致ABA问题的原因是两个线程的工作时间差距太大。例如线程a需要10秒,线程b需要2秒,它们同时从主线程中拿到x1并开始工作,在10秒中,线程b先把x1改成x2,x2改成x3。。。。最后x3又该回去x1,这时候线程a算出结果x4并对主内存中的变量进行CAS操作,通过比较期望值和现时变量的值发现是一致的,就认为这段时间里面没有其他线程对变量进行修改过,但是实际上,这个变量以及是被修改过多次了。

      那么我们改如何解决ABA问题呢?

      这里涉及到一个概念叫原子引用,在实现原子性的过程中,我们可以使用java 里面的atomic类,但是有时候一些数据类需要我们自己去定制,那这些类又怎么实现原子性呢。java里面有个类叫AtomicReference,是一个原子封装类,把我们自己定义的数据类传进去之后,就可以基于CAS实现原子性。下面举个例子

    import java.util.concurrent.atomic.AtomicReference;
    
    class User{
        String userName;
        int age;
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public User(String name, int age){
            this.userName = name;
            this.age = age;
        }
    
        public String toString(){
            return "userName: " + this.userName + ", age: " + this.age;
        }
    }
    
    public class AtomicReferenceDemo {
        public static void main(String[] args) {
            User u1 = new User("z3",22);
            User u2 = new User("li4", 34);
            AtomicReference<User> atomicReference = new AtomicReference<User>();
            atomicReference.set(u1);
            System.out.println(atomicReference.compareAndSet(u1,u2) + "	" + atomicReference.get().toString());
            System.out.println(atomicReference.compareAndSet(u1,u2) + "	" + atomicReference.get().toString());
        }
    }

      然后,在原子引用的基础上,延伸出了版本原子引用,就是在CAS的基础上,对主内存中的数据记录一个版本号(时间戳),这样在实现CAS的过程中,除了比对期望值和实际值是否相等之外,还会比对版本号是否有变动过,这样可以准确地知道数据究竟有没有被修改过,也可以有效地规避ABA问题。下面还是上一段代码:

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicReference;
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    public class ABADemo {
        static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100);
        static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100,1);
    
        public static void main(String[] args) {
    
            //ABA问题演示,最终t2线程成功修改了变量的值
            /*
            new Thread(()->{
                atomicReference.compareAndSet(100, 101);
                atomicReference.compareAndSet(101, 100);
            },"t1").start();
    
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicReference.compareAndSet(100, 200);
            },"t2").start();
             */
    
            //版本原子引用下解决ABA原子引用问题示例
            new Thread(()->{
                int stamp = atomicStampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + "	当前版本" + atomicStampedReference.getStamp() + "	当前值" + atomicStampedReference.getReference());
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
                stamp = stamp + 1;
                System.out.println(Thread.currentThread().getName() + "	当前版本" + atomicStampedReference.getStamp() + "	当前值" + atomicStampedReference.getReference());
                atomicStampedReference.compareAndSet(101,100,stamp,stamp+1);
                System.out.println(Thread.currentThread().getName() + "	当前版本" + atomicStampedReference.getStamp() + "	当前值" + atomicStampedReference.getReference());
            },"t3").start();
    
            new Thread(()->{
                int stamp = atomicStampedReference.getStamp();
                System.out.println(Thread.currentThread().getName() + "	当前版本" + atomicStampedReference.getStamp() + "	当前值" + atomicStampedReference.getReference());
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(atomicStampedReference.compareAndSet(100,200,stamp,stamp+1)){
                    System.out.println("t4 修改成功!!!");
                } else {
                    System.out.println("t4 修改失败!!!");
                }
            },"t4").start();
    
            while (Thread.activeCount() > 2){
                Thread.yield();
            }
    
            System.out.println(Thread.currentThread().getName() + "	当前版本" + atomicStampedReference.getStamp() + "	当前值" + atomicStampedReference.getReference());
        }
    }

      运行结果:

        t3 当前版本1 当前值100
        t4 当前版本1 当前值100
        t3 当前版本2 当前值101
        t3 当前版本3 当前值100
        t4 修改失败!!!
        main 当前版本3 当前值100

      从上面可以看到,线程t3将变量的值从100修改成101,再从101修改为100,最后t4想去将变量的值从100修改为200的时候,修改失败,因为t4一开始拿的版本号是1,最后去做写操作的时候发现版本号是3,虽然变量的值都是100,但是变量明显被人修改过了,因此修改失败。

     

     

  • 相关阅读:
    Silverlight中弹出网页
    Silverlight中嵌套html、swf、pdf
    silverlight 用户浏览器未安装SL插件问题
    silverlight视频、音频
    Silverlight形状、画笔、变换、图像处理、几何图形
    Silverlight动画学习笔记(三):缓动函数
    Silverlight动画的基本知识、关键帧动画
    Silverlight独立存储
    关于Silverlight调用天气预报接口问题
    c#获取今天星期几
  • 原文地址:https://www.cnblogs.com/QicongLiang/p/13641067.html
Copyright © 2011-2022 走看看