在这里插入图片描述
在 Java 并发编程中,我们经常面临一个选择:
如果想让一个整数 i 安全地加 1,该怎么办?

  • 方案 A (悲观派): 使用 synchronized 锁住代码块。

  • 后果: 安全,但太重了。就像为了防止有人插队,把整个售票厅大门都锁上,一个人办完业务才放下一个。

  • 方案 B (乐观派): 使用 AtomicInteger

  • 后果: 飞快!它没有用锁,却保证了线程安全。

这就引出了 Java 并发包(J.U.C)的基石——CAS (Compare And Swap)


💻 一、技术分析:一种“赌徒”心态

1. 什么是 CAS?

CAS 是一条 CPU 原子指令(cmpxchg)。它的核心思想是乐观锁
它在修改数据时,不加锁,而是抱着一种“赌徒”心态:“我猜根本没人跟我抢,我现在就去改。如果真有人抢了,我再重试。”

2. 三个关键操作数

CAS 指令执行时,需要三个参数:

  • V (Memory Value): 内存里真正存的值(比如现在的余额 100)。
  • A (Expected Old Value): 我以为的值(旧预期值,我也以为是 100)。
  • B (New Value): 我想修改成的新值(比如 110)。

操作逻辑:

  • CPU 去检查:“内存里的 V 等于 A 吗?”
  • 如果相等 (V == A): 说明期间没人动过。好,把 V 更新成 B (110)。修改成功。
  • 如果不等 (V != A): 说明有人插队改过了(V 变成了 105)。修改失败,不许动内存。

3. 自旋 (Spinning)

如果修改失败了怎么办?放弃吗?
不。通常会配合一个**“死循环”**。
失败了 -> 重新读 V 的最新值 -> 重新计算 -> 再次尝试 CAS。
一直转圈圈,直到成功为止。这叫 自旋锁


👔 二、故事场景:试衣间的“贴标签”游戏

为了搞懂 CASSynchronized 的区别,我们将 修改共享变量 比作 更衣室换衣服

1. Synchronized —— “上锁的更衣室”

  • 场景: 只有一个更衣室,门上有把大锁。

  • 流程:

  • 张三进去了,“咔嚓” 把门反锁。

  • 李四、王五来了,推门推不开,只能在门口排队睡觉(线程阻塞)。

  • 张三换完出来,叫醒李四。

  • 评价: 悲观锁。认为总有人会冲进来,所以必须先锁门。线程切换成本高(叫醒服务很贵)。

2. CAS —— “开放式贴标签”

  • 场景: 这里的规则是,衣服挂在墙上,大家都可以拿,但要给衣服贴上新价格

  • 流程:

  • 第一步 (Read): 张三看到墙上的衣服标价是 100 元。

  • 第二步 (Calculate): 张三心里想:“我要把它改成 110 元。”

  • 第三步 (CAS 核心): 张三拿着“110”的标签冲上去,贴之前最后确认一眼:“现在的价格还是 100 吗?”

  • 情况 A: 还是 100。啪!贴上 110。成功

  • 情况 B: 居然变成了 105(被李四抢先改了)。张三不贴了

  • 第四步 (Spin): 张三没放弃。他看了一眼现在的 105,重新计算:“那我改成 115 吧。” 再次冲上去尝试。

  • 评价: 乐观锁。全程没有锁门,大家都在飞快地跑。只要没人抢,速度极快。


😈 三、致命缺陷:ABA 问题(前女友陷阱)

CAS 看起来很完美,但它有一个著名的逻辑漏洞。

1. 什么是 ABA?

CAS 检查的是:“现在的值是不是 100?”
如果 ,它就认为“没变过”。

但有没有一种可能:

  1. 原来的值是 A (100)
  2. 中间有个线程把它改成了 B (50)
  3. 又有一个线程把它改回了 A (100)
  4. 你的 CAS 过来检查:“哟,还是 100,没人动过!” -> 修改成功

虽然值没变,但“经历”变了。 这在某些场景下是致命的(比如栈结构、内存回收)。

2. 故事比喻:那杯水被喝过吗?

  • 场景: 你桌上放了一杯满的水 (A)。你离开了一会儿。

  • 过程:

  • 你的室友过来,把水喝光了 (A -> B)。

  • 他觉得不好意思,又接了一杯自来水放回去 (B -> A)。

  • 结果: 你回来了。你用 CAS 眼神确认:“水还是满的 (A)”。于是你端起来喝了。

  • 问题: 你以为是原来的纯净水,其实是自来水。你被骗了。

3. 解决方案:加版本号 (AtomicStampedReference)

怎么解决?给水杯贴个封条(版本号)。

  • 原来是 100 (v1)
  • 改成 50 后变成 50 (v2)
  • 改回 100 后变成 100 (v3)
  • 你的 CAS 检查时:不仅要求值是 100,还要求版本号是 v1。v1 != v3,由于版本对不上,修改失败。

🎯 四、总结:无锁虽好,由于 CPU 烫手

CAS 是 Java 高性能并发(J.U.C 包)的基石,但它不是银弹。

  1. 优点: 。没有线程阻塞和上下文切换的开销。
  2. 缺点:
  • CPU 开销大: 如果竞争太激烈,张三一直在“自旋”重试,CPU 会被空转跑满(风扇狂转)。
  • ABA 问题: 需要用版本号解决。

使用建议

  • 并发量不高(冲突少) -> 用 CAS (Atomic 类)。
  • 并发量极高(冲突多,一直自旋不划算) -> 还是乖乖用 Synchronized 吧。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐