当前位置: 首页 > 科技观察

面试官很喜欢问的CAS

时间:2023-03-23 11:39:31 科技观察

,在文末本文转载自微信公众号《程序员巴士》,作者tech-bus.71。转载本文请联系程序员巴士公众号。前言自学JAVA一年,阿爸阿爸终于约上了面试。这次面试官让她谈谈对CAS的理解。回去等通知。如果对CAS一无所知的同学,建议阅读相关博客,了解基本原理,然后在面试时如何回答面试官:DoyouknowanythingaboutCAS?你能谈谈吗?AbbaAbba:理解一些,CAS的全称是CompareAndSwap,即比较和交换。阿爸阿爸:CAS的思路比较简单,主要涉及三个值:当前内存值V,期望值(旧内存值)O,要更新的内存值U,当且仅当期望值O与当前内存值相同时,当值V相等时,将内存值V修改为更新后的值U并返回true,否则返回false。面试官:还有什么事吗?你知道CAS的使用场景吗?阿爸阿爸:呃……应该差不多吧。CAS似乎用于并发包。面试官:好的,CAS有什么缺点吗?阿爸阿爸:呃……好吧……好像是ABA的问题,好像用AtomicStampedReference解决了。面试官:还有其他不足吗?阿爸阿爸:呃……记不太清了……面试官:好的,那你先回去等通知吧??阿爸阿爸:好的~现场发offer面试官:你懂CAS吗?说说Abaaba:CAS的全称是CompareandSwap,即比较和交换。阿爸阿爸:CAS的思路比较简单,主要涉及三个值:当前内存值V,期望值(旧内存值)O,要更新的内存值U,当且仅当期望值O与当前内存值相同时,当值V相等时,将内存值V修改为更新后的值U,返回true,否则返回false。阿爸阿爸:CAS主要是在一些需要加锁的场景下作为一种乐观锁的解决方案。一般在一些需要加锁又不想引入加锁场景的简单操作中,会使用CAS来代替锁。阿爸阿爸:CAS主要涉及三个问题:ABA问题,自旋带来的消耗,CAS只能是单变量的。采访者:您能详细解释一下这三个问题吗?AbbaAbba:ABA问题是指当一个线程t1在进行CAS操作时,其他线程t2把变量A改成B,再改成A,这时候t1发现A没有变,于是交换操作执行,因为在执行交换操作之前,其实变量A发生了变化,但最后又变回了A。这个A不是另一个A,这个时候exchange操作在某些业务场景下可能会出现问题。解决ABA问题有两种解决方案。阿爸阿爸:方案一:在对变量进行操作的时候给变量加上一个版本号,每次操作变量时版本号加1,在数据库的乐观锁中经常看到。阿爸阿爸:方案二:Java提供了对应的原子引用类AtomicStampedReference,通过包裹[E,Integer]的元组来标记对象的版本,从而避免ABA问题。阿巴巴:自旋对CAS的消耗如果自旋长时间失败,会给CPU带来很大的开销阿巴巴:解决方法:1、在代码层面销毁for循环,设置合适的循环次数。2、使用JVM可以支持处理器提供的暂停指令,提高效率。它可以延迟流水线执行指令,避免消耗过多的CPU资源。阿爸阿爸:CAS只能用于单个变量。对于一个共享变量,可以使用CAS方式来保证原子操作,但是当有多个共享变量时,就不能使用CAS来保证原子性了。从JDK1.5开始,提供了AtomicReference类来保证被引用对象的原子性,可以将多个变量放在一个对象中进行CAS操作。阿巴阿巴:JDK1.5中新增的java.util.concurrent(JUC)是基于CAS的。一般来说,CAS等乐观锁适用于读多写少的场景。面试官见阿爸阿爸答得流利,决定刁难她。采访者:你知道JMM吗?告诉我关于JMM的事。阿爸阿爸:我知道一点,JMM就是JAVA内存模型(JAVAMemoryModel),目的是为了屏蔽各种硬件和操作系统之间的内存访问差异,让JAVA程序在各种平台上访问内存的方式一致。阿爸阿爸:不仅如此,JMM还规定所有的变量都存放在主存中,每个线程都有自己独立的工作空间。当一个线程对变量进行操作时,它必须先从主存中读取自己的工作。然后在内存中操作,最后写回主存。AbbaAbba:关于主内存和工作内存的交互,JAVA定义了八个操作来完成,而且这些操作都是原子的:lock,unlock,read,load,use,assign,store,write。采访者:是的,是的。JMM真的存在吗?和JVM内存模型(JAVA虚拟机内存模型)一样吗?实现可能仍与模型不同。JMM和JVM不同,不分一个层次,基本上无所谓。堆和方法区是线程共享的,虚拟机栈、本地方法栈、程序计数器是线程私有的。程序计数器是这些区域中唯一不发生OOM的区域。面试官:你理解的很好,说说Volatile关键字吧。阿爸阿爸:Volatile可以说是JAVA虚拟机提供的最轻量级的同步机制。当一个变量被定义为volatile时,它??将具有两个特征。首先是要保证该变量对所有线程都可见性,即当一个线程改变这个变量的值时,其他线程可以立即感知到。虽然是可见的,但是在并发情况下多个线程对volatile修饰变量进行操作时,会存在线程安全问题。的。这是因为volatile修饰的变量在各个线程的工作内存中是不一致的,但是由于每次使用都会刷新,所以执行引擎是看不到不一致的。AbbaAbba:Volatile修饰的变量的第二个特点是禁止指令重排序优化。普通变量只是保证所有依赖的赋值结果在方法执行过程中都能得到正确的结果。不能保证赋值顺序和代码中写的顺序一致。比如下面DCL的单例模式。publicclassinstance{privateStringstr="";privatevolatilestaticinstanceins=null;/***构造函数私有化*/privateinstance(){str="hi";}/***DCL获取单例*@return*/publicstaticinstancegetinstance(){if(ins==null){synchronized(instance.class){if(ins==null){ins=newinstance();}}}returnins;}}abaaba:如果上面的ins变量没有用volatile变量修饰,那么当threadA获取instance.class锁,并用ins=newinstance()初始化ins变量,由于指令较多,jvm可能会乱序执行。此时如果线程B正在执行if(ins==null),一般情况下,如果为true,说明需要获取instance.class锁,等待初始化。但是此时假设线程A还没有再次初始化ins,比如只是分配了空间,还没有构造对象,但是引用已经返回,所以线程B得到了一个还没有完全实例化的对象,从而发生异常。加上volatile关键字后,如果实例还没有初始化,它的引用不会被释放,从而避免异常。面试官:对,你对这方面的掌握很扎实,明天就可以来上班了。阿爸阿爸:好吗??