当前位置: 首页 > news >正文

并发 - 原子类与 CAS 原理

知识点 11:并发编程 —— 原子类与 CAS 原理

1. 核心理论:什么是原子操作?

在并发编程中,原子操作指的是一个不会被线程调度机制中断的操作。这种操作一旦开始,就一直运行到结束,中间不会有任何上下文切换。我们之前讨论过,count++ 不是一个原子操作,它包含“读-改-写”三个步骤,因此在多线程环境下是不安全的。

为了解决这个问题,除了使用锁(悲观锁)之外,Java 还提供了一种更高效的“无锁”解决方案——原子类

原子类位于 java.util.concurrent.atomic 包下,例如 AtomicInteger, AtomicLong, AtomicBoolean 等。它们通过一种名为 CAS (Compare-And-Swap) 的机制,以一种乐观的方式保证了复合操作的原子性。


2. 深度剖析:悲观锁 vs 乐观锁

在并发控制中,根据对冲突的“态度”不同,我们可以将锁分为两大类:悲观锁和乐观锁。

2.1 悲观锁 (Pessimistic Locking)

  • 核心思想: 非常悲观,它总是假设最坏的情况,即每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
  • 典型实现: Java 中的 synchronized 关键字和 ReentrantLock 等独占锁,都是悲观锁的实现。它们在操作数据之前,会先获取锁,确保在整个操作过程中,只有一个线程能访问数据。
  • 适用场景: 适合写多读少的场景,即冲突发生的概率很高。如果冲突频繁,使用悲观锁可以避免乐观锁大量的重试操作,反而能提高性能。

2.2 乐观锁 (Optimistic Locking)

  • 核心思想: 非常乐观,它总是假设最好的情况,即每次去拿数据的时候都认为别人不会修改。所以它不会上锁,而是在更新的时候会判断一下在此期间别人有没有去更新这个数据。
  • 典型实现: CAS (Compare-And-Swap) 机制就是乐观锁的典型实现。它在更新前,会先比较当前内存中的值与自己之前取到的值是否一致。如果一致,就执行更新;如果不一致,就认为有冲突,然后进行重试(自旋),直到成功为止。
  • 适用场景: 适合读多写少的场景,即冲突发生的概率很低。因为不上锁,省去了锁的开销,可以获得更高的吞吐量。

3. CAS (Compare-And-Swap) 原理

CAS 是实现乐观锁和原子类的核心技术,是现代 CPU 广泛支持的一种原子指令

  • CAS 指令包含三个操作数

    1. 内存位置 V (要更新的变量的内存地址)
    2. 预期原值 A (我们认为这个变量现在应该是什么值)
    3. 新值 B (如果变量的值和我们预期的 A 一样,就把它更新成 B)
  • 执行过程: 当执行 CAS 指令时,CPU 会原子地完成以下判断和操作:当且仅当内存位置 V 的值等于预期原值 A 时,CPU 才会将该位置的值更新为新值 B。否则,它什么也不做。无论成功与否,它都会返回操作前的旧值。

AtomicInteger.getAndIncrement() 的工作流程 (即 i++ 的原子版)**:

`AtomicInteger` 能保证复合操作的原子性,其秘诀就在于 **“CAS + 循环重试(自旋)”**。1.  在一个**无限循环**(`do-while` 循环,也常被称为**自旋 (Spinning)**)中进行。
2.  **读取**当前 `AtomicInteger` 的 `value` 值(这是一个 `volatile` 变量),我们称之为 `current`。这个值就是我们的“预期原值 A”。
3.  计算出“新值 B”,即 `current + 1`。
4.  调用 `compareAndSet(current, current + 1)` 方法,这个方法会执行底层的 CAS 原子指令,尝试将内存中的值从 `current` 更新为 `current + 1`。
5.  **检查 CAS 结果**:`compareAndSet` 会返回 `true` 或 `false`。-   如果返回 `true`(成功了),意味着在“读-改-写”的瞬间,没有其他线程修改过这个值。循环结束,操作完成。-   如果返回 `false`(失败了),意味着在我们准备写入时,有其他线程已经抢先修改了值。循环**不会结束**,而是会**回到第 2 步**,重新读取最新的值,然后再次尝试 CAS。这个“失败后重试”的过程就是**自旋**。

3.1 CAS 的潜在问题

  1. ABA 问题:

    • 问题描述: 这是 CAS 的一个经典漏洞。如果一个变量的值从 A 变成了 B,然后又变回了 A。当一个线程执行 CAS 时,它会检查发现当前值是 A,与预期值 A 相符,于是执行更新。但它不知道这个值其实中间被改动过。
    • 解决方案: JUC 包提供了 AtomicStampedReference 类来解决这个问题。它通过为每个值关联一个额外的“版本号”(stamp),CAS 操作不仅要检查值是否相等,还要检查版本号是否相等,从而避免了 ABA 问题。
  2. 自旋时间长,开销大: 其实就是 CAS 的开销问题

    • 问题描述: 如果锁的竞争一直很激烈,会导致线程反复地尝试 CAS 操作但一直失败。这个“自旋”的过程会持续占用 CPU,造成大量的计算资源浪费。
    • 结论: 在高竞争的环境下,synchronized 这种能让线程进入阻塞等待状态的“重量级锁”,其性能表现反而可能会优于 CAS 这种需要不断自旋的乐观锁。

4. 生活中的例子与代码示例

  • 生活比喻: 想象你在拍卖会上竞拍一件物品。

    • 共享变量: 当前的最高出价
    • 你的操作 (increment): 你想在当前最高出价的基础上加 100 元。
    • CAS 流程:
      1. 你看到当前的最高出价是 1000 元(读取预期原值 A)。
      2. 你决定出价 1100 元(计算新值 B)。
      3. 你举牌喊价(执行 CAS)。在喊价的瞬间,拍卖师会原子地做一次判断:
        • 成功: 如果在你喊价之前,最高出价仍然是 1000 元,那么你的出价成功,最高价更新为 1100 元。
        • 失败: 如果在你举牌的瞬间,另一个人(其他线程)已经抢先喊出了 1050 元,那么拍卖师会认为你的出价(基于 1000 元)无效。你必须重新听一下现在的最高价是 1050 元,然后在这个基础上再次出价(自旋重试)。
  • 核心代码示例: 我们用原子类来改造之前的 Counter 实验。

package com.study.concurrency;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;// 使用 AtomicInteger 实现的线程安全的计数器
class AtomicCounter {// 1. 使用 AtomicInteger 作为计数器private AtomicInteger count = new AtomicInteger(0);public void increment() {// 2. getAndIncrement() 方法本身就是原子的,它内部封装了 CAS 循环count.getAndIncrement();}public int getCount() {return count.get();}
}public class AtomicTest {public static void main(String[] args) throws InterruptedException {final AtomicCounter counter = new AtomicCounter();ExecutorService threadPool = Executors.newFixedThreadPool(10);Runnable task = () -> {for (int i = 0; i < 1000; i++) {counter.increment();}};for (int i = 0; i < 10; i++) {threadPool.submit(task);}threadPool.shutdown();while (!threadPool.awaitTermination(1, TimeUnit.SECONDS)) {System.out.println("线程池仍在运行...");}System.out.println("\n所有任务执行完毕。");System.out.println("AtomicCounter 的最终值为: " + counter.getCount());System.out.println("预期值应为: 10000");}
}
http://www.jsqmd.com/news/279381/

相关文章:

  • 并发 - Callable 与 Future
  • 麦橘超然性能压测报告:单次生成耗时统计
  • 2026营口市英语雅思培训辅导机构推荐;2026权威出国雅思课程排行榜
  • fft npainting lama高阶使用技巧:分层修复与边缘羽化实战案例
  • 企业级通信如何选型?(MCP与OpenAI Function Calling技术对决揭秘)
  • OOP 经典对比
  • YOLOv11+BiFPN革新小麦杂质检测技术
  • 手把手教你实现MCP服务器resources热更新,动态调整不再重启服务
  • 山石网科各硬件产品Console配置口波特率汇总
  • 揭秘Dify Iteration节点:如何高效处理复杂列表数据?
  • Java集合类框架的基本接口有哪些?
  • 为什么FSMN VAD总检测失败?参数调优实战教程入门必看
  • 基于51单片机智能手环老人防跌倒报警器GSM短信上报设计套件106(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码
  • 强化学习十年演进
  • Live Avatar降本部署方案:单GPU+CPU offload低配环境实操教程
  • RTX5060显卡对PyTorch与CUDA适配问题解决方案(解决环境依赖问题AI微调部署前奏)
  • 2026锦州市英语雅思培训辅导机构推荐;2026权威出国雅思课程排行榜
  • 紧急警告:错误配置导致Claude Desktop丢失MCP Server连接(附修复方案)
  • 广东激光熔敷公司怎么选,哪家口碑好?
  • GEO优化公司推荐哪家好?从技术深度到服务能力的权威解析!
  • Pinterest注册失败怎么办?2026最新解决指南在这里
  • Unsloth资源占用监控:GPU显存与CPU使用率跟踪方法
  • Paraformer-large语音识别合规性:金融行业落地实践
  • 盘点人工智能转型服务方案,广东省哪家口碑好费用低
  • 【Dify部署避坑指南】:解决上传文件413错误的5种高效方案
  • 分析成都太阳能板定制厂家,员工素质哪家高
  • 2026 AEO认证咨询推荐:专业服务助力企业通关效率提升
  • 【Web安全】什么是XSS攻击?如何实现手动XSS,利用BeEF执行XSS攻击?
  • 写论文找不到外国文献?方法合集来了!实用检索技巧助你高效获取外文文献资源
  • Java 开发中的良好的小习惯