今天的故事从一个面试现场开始……
小米最近在准备 Java 高级开发的社招面试。为了拿到大厂 Offer,他疯狂刷题、复习各种并发知识,甚至把《Java 并发编程艺术》翻了好几遍!
终于,他迎来了自己的第一场面试。
面试官:你知道什么是 CAS 吗?
小米端正坐姿,微微一笑:“当然知道!CAS,全称Compare And Swap(比较并交换),是 Java 并发编程中用于实现无锁操作的一种技术。”
“简单来说,CAS 主要用于原子性操作,它的原理就是:先比较,再交换。当多个线程竞争时,只有当变量的当前值与期望值相等时,才会执行更新,否则更新失败。”
面试官点点头:“那你能详细讲讲它的实现原理吗?”
CAS 的底层原理小米自信地继续:“在 Java 中,CAS 主要依赖于Unsafe 类和CPU 的原子指令来实现的。”
核心方法:CAS 主要由 Unsafe 类的 compareAndSwapXXX 方法实现,比如 compareAndSwapInt()。
底层原理:Unsafe 类的 compareAndSwapXXX 方法,底层会调用CPU 指令(如 x86 平台上的 cmpxchg 指令),保证 CAS 操作是原子的,不会被其他线程中断。
无锁并发:CAS不需要加锁,避免了传统 synchronized 造成的线程阻塞,提高了性能。
小米为了让面试官更直观地理解 CAS,举了一个例子:
假设你去 ATM 取钱,先看账户余额 1000 元,然后取出 500 元。如果在你取钱的瞬间,另外一个线程(你的朋友)也在取钱,你的银行账户余额可能就会变得混乱!
而 CAS 就相当于 ATM 取款的过程:
先检查(比较):当前余额是否仍然是 1000 元?
如果是,执行扣款操作(交换),余额变成 500 元;
如果不是,说明有别的线程修改了余额,就重试,直到成功!
这时候,面试官满意地点了点头。
面试官:CAS 能保证线程安全吗?小米摇摇头:“CAS 只是一种原子操作,但它不能完全保证线程安全,因为它存在一些潜在问题。”
“比如:ABA 问题、自旋开销大、只能更新单个变量。”
ABA 问题面试官接着问:“能不能详细解释一下 ABA 问题?”
小米回答:“CAS 只会比较变量的值有没有发生变化,但不会关心值是否经历过其他变化。比如:”
线程 T1 读取变量 A,发现它的值是 10。
线程 T2 将 A 的值从 10 变成 20,然后又变回 10。
线程 T1 继续执行 CAS 操作,发现 A 仍然是 10,CAS 成功!
但实际上,A 的值已经被别的线程改过了!这就可能导致数据异常!
就像你在排队买奶茶,刚走开去上个厕所,回来发现排队的还是同一个人,但你不知道他其实已经买过一杯了!
如何解决 ABA 问题?使用 AtomicStampedReference!
Java 提供了 AtomicStampedReference,它在变量值的基础上增加了版本号(时间戳),每次更新都带上版本号,就能检测到变量是否真的被修改过。
示例代码:
小米继续说道:“CAS 是一种乐观锁,它不阻塞线程,而是不断尝试更新,如果失败了就一直重试,这就是‘自旋’。”
如果 CAS 失败次数多,比如高并发下竞争激烈,就会一直循环重试,导致CPU 资源被大量消耗,影响系统性能。
解决方案:
降低 CAS 失败率,比如减少高并发写操作的频率。
使用 synchronized 作为兜底方案,当 CAS 失败次数过多时,退而使用 synchronized 来减少 CPU 资源浪费。
使用 LongAdder 代替 AtomicLong,在高并发下减少冲突。
只能更新单个变量面试官继续追问:“CAS 是否可以一次更新多个变量?”
小米答道:“不可以,CAS 只能保证一个变量的原子性,如果要同时更新多个变量,CAS 就无能为力了。”
就像你要同时修改银行账户的余额和交易记录,CAS 只能更新其中一个!
解决方案:
使用 AtomicReference处理多个变量
使用 synchronized 或 Lock来保证多个变量的原子性
示例代码:
小米一口气回答道:
优点:
高效无锁:避免了 synchronized 的开销,提高并发性能。
硬件级保证:底层依赖 CPU 指令,能保证原子性。
适用于读多写少的场景:如 AtomicInteger、AtomicLong、ConcurrentHashMap。
缺点:
ABA 问题:可以使用 AtomicStampedReference 解决。
高并发下自旋浪费 CPU:可以配合 synchronized 使用。
只能更新单个变量:可以使用 AtomicReference 解决。
面试官微笑:“不错,你的回答很全面!”
小米心想,自己离大厂 Offer 又近了一步!
END今天我们通过面试的形式讲解了CAS 的概念、原理、问题以及优化方案,如果你也在准备 Java 高级开发的社招面试,希望这篇文章能帮到你!
如果你觉得有收获,别忘了点赞+关注,后续还有更多并发编程干货等着你!
我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!