栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

CAS面试题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

CAS面试题

1.讲一讲AtomicInteger类,为什么要用CAS而不是synchronized?
1.1 CAS(全称为compare-And-Swap)是什么?
它是一条CPU并发原语,它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新值,这个过程是原子的 。
CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

// 5是初始值
AtomicInteger atomicInteger=new AtomicInteger(5);
//5是期望值,如果现在主内存里的值也是5就改为2019
System.out.println(atomicInteger.compareAndSet(5, 2019)+"==update=="+atomicInteger.get());
5是期望值,如果现在主内存里的值也是5就改为1024
 System.out.println(atomicInteger.compareAndSet(5, 1024)+"==update=="+atomicInteger.get());

执行结果为:

1.2 为什么AtomicInteger不加synchronized能实现原子性?
示例volatile不保证原子性,而AtomicInteger能实现原子性。
volatile不保证原子性如下:

public class test{
  public static void main(String[] args) throws InterruptedException {
        MyData myData=new MyData();
		for(int i=1;i<=20;i++){
		    new Thread(()->{
                for(int k=1;k<=1000;k++){
                    myData.add();
                }
            },String.valueOf(i)).start();
        }
		//需要等待上面20个线程全部计算完成后,再用main线程取得最终的结果值是多少
        //activeCount()>2为什么是大于2因为默认两个线程,一是main线程二是gc线程
        //大于2时让出cpu让上面20个线程执行
		while (Thread.activeCount()>2){
		    Thread.yield();
		    log.info("Thread.activeCount()=={}",Thread.activeCount());
        }
        System.out.println(Thread.currentThread().getName()+"finally i 
        value"+myData.num);
	}    
}

class MyData{
        volatile int num;
        public void add(){
          num++;
       }
   }

执行结果为,期望得到20000,实际得到的值是19713:

 用AtomicInteger保证原子性:

public class test{
   public static void main(String[] args) throws InterruptedException {
        MyData myData=new MyData();
		for(int i=1;i<=20;i++){
		    new Thread(()->{
                for(int k=1;k<=1000;k++){
                     myData.add();
                    myData.addAtomic();
                }
            },String.valueOf(i)).start();
        }
		//需要等待上面20个线程全部计算完成后,再用main线程取得最终的结果值是多少
        //activeCount()>2为什么是大于2因为默认两个线程,一是main线程二是gc线程
        //大于2时让出cpu让上面20个线程执行
		while (Thread.activeCount()>2){
		    Thread.yield();
		    log.info("Thread.activeCount()=={}",Thread.activeCount());
        }
        System.out.println(Thread.currentThread().getName()+"int type  value"+myData.num);
        System.out.println(Thread.currentThread().getName()+"atomic type  value"+myData.atomicInteger);
	}
}

class MyData{
    volatile int num;
    public void add(){
        num++;
    }
    AtomicInteger atomicInteger=new AtomicInteger();
    public void addAtomic(){
        atomicInteger.getAndIncrement();
    }
}

执行结果为 :用AtomicInteger加1的结果是20000是正确的

16:10:07.575 [main] INFO com.hy.controller.ResController - Thread.activeCount()==18
main  int type  value19880
main  atomic type  value20000
AtomicInteger执行过程源码:
 AtomicInteger atomicInteger=new AtomicInteger();
    public void addAtomic(){
        atomicInteger.getAndIncrement();
    }

// getAndIncrement方法 this是当前对象valueOffset是内存偏移量,加1
 public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
 }

//Unsafe类的getAndAddInt方法,该Unsafe类是在jdk里的rt.jar包

   public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            //var1对象里的var2这个内存偏移量的值 
            var5 = this.getIntVolatile(var1, var2);         
            //var1 AtomicInteger 对象本身
           //var2 该对象值得引用地址
          //var4 需要变动的数量 +1
         //var5 是用var1,var2找出的主内存中真实的值
        //用该 对象当前的值与var5比较
        //如果相同,更新var5+var4并且返回true
       //如果不同,继续取值然后 再比较,直到更新完成
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        return var5;
    }
while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4))的执行流程:
 假设线程A和线程B两个线程同时执行getAndAddlnt操作(分别跑在不同CPU上) 
1 AtomicInteger里 面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一  份值为3的value的副本分别到各自的工作内存。 
2 线程A通过getlIntVolatile(var1, var2)拿到value值3, 这时线程A被挂起。
3 线程B也通过getlntVolatile(var1, var2)方法获取到value值3,此时刚好线程B没有被挂起并执行compareAndSwaplnt方法,比较内存值也为3,成功修改内存值为4,线程B打完收工,一切OK。 4 这时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值数字3和主内存的值数字4不一致,说明该值已经被其它线程抢先一步修改过了,那A线程本次修改失败,只能重新读取重新来一遍了。
5 线程A重新获取value值, 因为变量value被volatile修饰, 所以其它线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。 


AtomicInteger 源码:

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // 创建Unsafe 对象
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
               //得到Unsafe 的内存偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    //getAndIncrement方法里加1就是对value对1,因为是volatile 所以对其它线程可见
    private volatile int value;
}

为什么AtomicInteger不加synchronized能实现原子性?答案:
1.Unsafe是CAS的核心类,由于java方法无法直接访问底层系统,需要 通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据,Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为java中CAS操作的执行依赖于Unsafe类的方法。注意,Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。
2.变量valueOffset表示该变量值在内存中偏移地址,因为Unsafe就是根据内存偏移地址获取数据的

// getAndIncrement方法 this是当前对象valueOffset是内存偏移量,加1
 public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
 }

3.变量value用volatile修饰,保证了多线程之间的可见性
 DCL(双端检锁)机制不一定线程安全,原因是有指令重排序的存在,加入volatile可以禁止指令重排序。
原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能还没有完成初始化,instance=new SingletonDemo();可以分为以下3步完成
memory=allocate()  //1分配对象内存空间
instance(memory);  //2初始化对象
instance=memory; //3设置instance指向刚分配的内存地址,此时instance!=null
步骤2和步骤3不存在数据依赖关系,可能会指令重排序如下:
memory=allocate()  //1分配对象内存空间
instance=memory;  //3设置instance指向刚分配的内存地址,此时instance!=null,但是对象还没有初始化完成
instance(memory);  //2初始化对象
指令重排只会保证串行语义的执行一致性(单线程),但并不会关心多线程间的语义一致性
所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。

以下代码是DCL(双端检锁):

class SingletonDemo{
    private SingletonDemo(){
        System.out.println("我是一个构造函数");
    }
    private static SingletonDemo instance=null;
    public static SingletonDemo getInstance(){
        if(instance==null){
            synchronized (SingletonDemo.class){
                if(instance==null){
                    instance=new SingletonDemo();
                }
            }
        }
        return instance;
    }
}

禁止指令重排加volatile:

class SingletonDemo{
    private SingletonDemo(){
        System.out.println("我是一个构造函数");
    }
    private static volatile SingletonDemo instance=null;
    public static SingletonDemo getInstance(){
        if(instance==null){
            synchronized (SingletonDemo.class){
                if(instance==null){
                    instance=new SingletonDemo();
                }
            }
        }
        return instance;
    }
}
CAS缺点:
循环时间长,开锁大
只能保证一个共享变量的原子操作。
会导致ABA问题,所谓ABA就是线程A把自己工作内存里的值改成别的值同步到主内存,然后又改回原来的值同步到主内存,过后线程B拿自己工作内存的值和主内存的值比较是一样然后更改。线程B并不知道A中间已经改过一次。用 AtomicStampedReference的时间戳版本号解决ABA问题,就是版本号比较,类似于Svn
public class ABADemo {
    static AtomicReference atomicReference=new AtomicReference(100);
    static AtomicStampedReference atomicStampedReference=new AtomicStampedReference(100,1);

    public static void main(String[] args) {
        System.out.println("=======以下是ABA问题的产生=======");
        new Thread(new Runnable() {
            public void run() {
                atomicReference.compareAndSet(100,101);
                atomicReference.compareAndSet(101,100);
            }
        },"t1").start();

        new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("更改是否成功="+atomicReference.compareAndSet(100,2019)+"=="+atomicReference.get());
            }
        },"t2").start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("=======以下是ABA问题的解决=======");
        new Thread(new Runnable() {
            public void run() {
                int stamp=atomicStampedReference.getStamp(); //版本号
                System.out.println(Thread.currentThread().getName()+"第一次版本号:"+stamp);
                try {
                    Thread.sleep(1);  //为了让t4拿到 为1的版本号
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
                System.out.println(Thread.currentThread().getName()+"第二次版本号:"+atomicStampedReference.getStamp());
                atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
                System.out.println(Thread.currentThread().getName()+"第三次版本号:"+atomicStampedReference.getStamp());
            }
        },"t3").start();


        new Thread(new Runnable() {
            public void run() {
                int stamp=atomicStampedReference.getStamp(); //版本号
                System.out.println(Thread.currentThread().getName()+"第一次版本号:"+stamp);
                try {
                    Thread.sleep(3); //为了让t3执行完修改操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean b = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
                System.out.println(Thread.currentThread().getName()+"修改是否成功:"+b+"版本号为:"+atomicStampedReference.getStamp());
                System.out.println(Thread.currentThread().getName()+"值为:"+atomicStampedReference.getReference());

            }
        },"t4").start();
    }
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/703918.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号