🍂 枫言枫语:我是予枫,一名行走在 Java 后端与多模态 AI 交叉路口的研二学生。

“予一人以深耕,观万木之成枫。”

在这里,我记录从底层源码到算法前沿的每一次思考。希望能与你一起,在逻辑的丛林中寻找技术的微光。

发布时间:2026年1月13日


在 Java 并发编程的面试或日常开发中,我们经常会遇到一个绕不开的话题:

早期我们习惯用 synchronized 一把大锁保平安,但随着对性能要求的提高,java.util.concurrent.atomic 包下的原子类(基于 CAS)成为了高频客。这时常会引发一个思考:既然 synchronized 功能已经如此强大,且在 Java 6 之后经过了各种偏向锁、轻量级锁的优化,为什么我们还需要 CAS 呢?

今天,我们就来深度拆解这两者背后的设计哲学与应用场景。


一、 悲观与乐观:两种截然不同的哲学

要理解这两者,首先要理解它们对“竞争”的态度。

  • synchronized(悲观锁): 它认为“总有刁民想害朕”。每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。这样别人想拿这个数据就会阻塞,直到它拿到锁。

  • CAS(乐观锁): 它认为“世界很美好”。每次去拿数据的时候都认为别人不会修改,所以不上锁。但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。


二、 为什么需要 CAS?

虽然 synchronized 已经进化得很优秀,但在某些场景下,CAS 拥有无可比拟的优势。

1. 摆脱上下文切换的“重负载”

synchronized 发生真正的锁竞争时,未获取锁的线程会被挂起(Blocking)并进入内核态。

  • 代价: 线程的挂起和唤醒涉及操作系统在用户态与内核态之间的切换。这种切换需要保存寄存器、程序计数器等上下文信息,开销极大。

  • CAS 的解法: CAS 是非阻塞的。如果更新失败,它通常会选择“自旋”(Spin),即在一个循环里不停尝试。对于执行时间极短的代码(如计数器 i++),自旋等待的开销远小于线程切换。

2. 更细粒度的原子操作

如果你只想保护一个简单的变量,使用 synchronized 就像是为了切一个苹果而动用了整套手术器械。

  • 代码整洁度: 使用 AtomicIntegerincrementAndGet() 显然比写一个 synchronized 块更优雅。

  • 并发性能: 在低中度竞争下,CAS 直接通过 CPU 指令(如 x86 的 LOCK CMPXCHG)完成操作,效率极高。

3. 彻底告别死锁

synchronized 如果使用不当(嵌套加锁、锁顺序不一致),极易引发死锁,导致程序瘫痪。而 CAS 因为不需要“持有”锁,从逻辑上规避了死锁风险。


三、 深度对比:谁才是性能之王?

为了方便大家选型,我整理了这张对比表:

维度 synchronized (重量级优化版) CAS (Compare And Swap)
底层原理 Monitor 对象、操作系统互斥量 CPU 原子指令 (cmpxchg)
线程状态 失败则阻塞 (BLOCKED) 失败则自旋或重试 (RUNNABLE)
编程复杂度 简单,但需防范死锁 较复杂,需处理 ABA 等问题
资源消耗 竞争激烈时表现更稳定 竞争激烈时 CPU 占用极高
适用场景 大段逻辑同步、极高并发竞争 简单变量同步、中低并发、追求响应

四、 CAS 并非“银弹”

我们在推崇 CAS 的同时,也必须正视它的缺陷:

  1. ABA 问题: 变量从 A 变成 B 又变回 A,CAS 会认为它没变。

    • 方案: 加上版本号(如 Java 中的 AtomicStampedReference)。

  2. 自旋开销: 如果竞争极其激烈,线程会长时间占用 CPU 做无用功。

  3. 只能保证一个共享变量: 无法像 synchronized 那样保护一段复杂的代码逻辑。


总结

在现代 Java 开发中,我们不应该单纯地认为谁替代了谁,而应该组合使用

  • 如果你是在处理简单的计数、状态标记、单变量更新,请首选 CAS (Atomic系列)

  • 如果你需要保护一段复杂的业务逻辑、涉及多个变量的原子性,或者并发竞争已经激烈到让 CPU 飙升,那么 synchronized 依然是你最稳固的后盾。

编程之美,不在于追求最快的工具,而在于在复杂的业务场景中,找到那个最平衡的支点。

关于作者: 💡 予枫,某高校在读研究生,专注于 Java 后端开发与多模态情感计算。💬 欢迎点赞、收藏、评论,你的反馈是我持续输出的最大动力!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐