面试官:今天我们来聊聊CAS吧?你对CAS了解多少?
候选者:好,CAS的全称为compare and swap,比较并交换
候选者:虽然翻译过来是「比较并交换」,但它是一个原子性的操作,对应到CPU指令为cmpxchg
面试官:好家伙,CPU指令你都知道?
候选者:这没什么,都是背的。
面试官:….
候选者:回到CAS上吧,CAS的操作其实非常简单。
候选者:CAS 有三个操作数:当前值A、内存值V、要修改的新值B
候选者:假设 当前值A 跟 内存值V 相等,那就将 内存值V 改成B
候选者:假设 当前值A 跟 内存值V 不相等,要么就重试,要么就放弃更新
候选者:将当前值与内存值进行对比,判断是否有被修改过,这就是CAS的核心

面试官:确实,那为什么要用CAS呢?
候选者:嗯,要讲到CAS就不得不说synchronized锁了,它是Java锁…然后…
面试官:稍微打断一下,synchronized锁你稍微讲下就好了,后面会专门问的,在这不用细讲。
候选者:ok,其实就是synchronized锁每次只会让一个线程去操作共享资源
候选者:而CAS相当于没有加锁,多个线程都可以直接操作共享资源,在实际去修改的时候才去判断能否修改成功
候选者:在很多的情况下会synchronized锁要高效很多
候选者:比如,对一个值进行累加,就没必要使用synchronized锁,使用juc包下的Atomic类就足以。
面试官:了解,那你知道CAS会有什么缺点吗?
候选者:CAS有个缺点就是会带来ABA的问题
候选者:从CAS更新的时候,我们可以发现它只比对当前值和内存值是否相等,这会带来个问题,下面我举例说明下:
候选者:假设线程A读到当前值是10,可能线程B把值修改为100,然后线程C又把值修改为10。
候选者:等到线程A拿到执行权时,因为当前值和内存值是一致的,线程A是可以修改的!
候选者:站在线程A的角度来说,这个值是从未被修改的(:
候选者:这是不合理的,因为我们从上帝的角度来看,这个变量已经被线程B和线程C修改过了。
候选者:这就是所谓的ABA问题

候选者:要解决ABA的问题,Java也提供了AtomicStampedReference类供我们用,说白了就是加了个版本,比对的就是内存值+版本是否一致
面试官:嗯,了解。
面试官:阿里巴巴开发手册提及到 推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数),你能帮我解读一下吗?
候选者:AtomicLong做累加的时候实际上就是多个线程操作同一个目标资源
候选者:在高并发时,只有一个线程是执行成功的,其他的线程都会失败,不断自旋(重试),自旋会成为瓶颈
候选者:而LongAdder的思想就是把要操作的目标资源「分散」到数组Cell中
候选者:每个线程对自己的 Cell 变量的 value 进行原子操作,大大降低了失败的次数
候选者:这就是为什么在高并发场景下,推荐使用LongAdder 的原因(:
面试官:OK
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)