用 CAS 思维设计一个高并发系统(工程级)

内容纲要

本文是承接上一篇 CAS 基础文工程级进阶篇

目标只有一个:

让你具备一种能力:看到“高并发系统需求”,脑子里自动浮现 CAS 结构,而不是先想 synchronized / Lock。

这篇文章写完,你应该能用 CAS 思维设计系统,而不是只会用 CAS API


用 CAS 思维设计一个高并发系统(工程级)

开篇先给结论(非常重要)

CAS 不是一个“并发工具”,而是一种“系统设计思维”。

真正的高手不是“哪里都用 CAS”,而是:

  • 判断 哪里可以无锁
  • 判断 哪里必须退化成锁
  • 判断 失败成本是否可重试
  • 判断 状态是否可拆分

一、从“系统视角”重新理解 CAS

1. 普通人理解的 CAS

“CAS 是 AtomicInteger 用的东西。”

这是API 视角


2. 工程级理解的 CAS

CAS = 用失败重试,换取并发吞吐

换句话说:

  • 失败是允许的
  • 重试是设计的一部分
  • 一致性是最终达成的

这在系统层面意味着:

CAS 天然适合“非关键路径 + 可回滚 + 可重试”的操作


二、高并发系统的第一性问题

在设计任何高并发系统前,只问四个问题:

问题 1:这个操作失败了能不能重试?

  • 能 → CAS 候选
  • 不能 → 锁 / 事务

问题 2:这是“状态更新”还是“复杂逻辑”?

  • 单状态 → CAS
  • 多状态耦合 → 锁 / 拆状态

问题 3:冲突概率高不高?

  • 低 / 中 → CAS
  • 高 → 分片 / 锁 / 队列化

问题 4:是否是系统关键路径?

  • 非关键 → CAS
  • 关键路径 → 稳定优先

这四个问题,决定了 CAS 能不能用。


三、CAS 系统设计的核心套路(五大法则)

法则一:状态必须“原子化”

CAS 的第一前提:

你要操作的东西,必须能被抽象成一个“状态值”

例如:

场景 可 CAS 化的状态
订单 status
任务 state
连接 open / close
配额 count
开关 enabled

如果你说不清楚“我 CAS 的是什么状态”
👉 说明这个设计还不适合 CAS。


法则二:状态转移必须是“单向或有限图”

CAS 最怕:

  • 无限状态
  • 无规则跳转

工程上最好的形态是:

INIT → RUNNING → DONE

或:

NEW → PROCESSING → SUCCESS / FAIL

这是状态机 + CAS 的黄金组合。


法则三:把“失败”当成正常路径

很多系统一开始就错在这里。

错误思维:

CAS 失败 = 出问题了

正确思维:

CAS 失败 = 有竞争,继续重试

工程上你要明确设计:

  • 重试次数
  • 退避策略
  • 降级路径

法则四:拆热点,而不是硬抗

如果一个 CAS 点:

  • 所有线程都在争
  • 自旋失败率极高

不要优化 CAS 本身,要拆状态。

典型手段:

  • 分段(LongAdder)
  • 分桶(ConcurrentHashMap)
  • Shard Key
  • 局部计数 + 汇总

法则五:CAS + 锁,不是对立关系

工程级系统几乎从不纯 CAS

正确形态是:

CAS 解决 80% 快路径
锁兜住 20% 慢路径

四、一个完整工程示例:高并发任务调度系统

场景描述

你要设计一个系统:

  • 多个 worker 并发抢任务
  • 一个任务只能被执行一次
  • 不允许重复执行
  • 吞吐要求极高

1. 错误设计(锁优先)

synchronized(task) {
    if (task.status == NEW) {
        task.status = RUNNING;
        return task;
    }
}

问题:

  • 锁粒度大
  • 线程阻塞
  • 吞吐急剧下降

2. CAS 思维设计

抽象状态

task.status = NEW / RUNNING / DONE

核心代码模型

if (CAS(task.status, NEW, RUNNING)) {
    return task;
}

含义是:

谁先 CAS 成功,谁拿到任务

失败的线程:

  • 不等待
  • 不阻塞
  • 直接抢下一个任务

3. 失败成本分析(关键)

CAS 失败意味着:

  • 任务已经被别人拿走
  • 本线程什么也没损失

👉 这是完美 CAS 场景


4. 极端情况兜底

  • 任务长时间 RUNNING
  • worker 崩溃

解决方案:

  • 状态 + version / timestamp
  • CAS + 超时重置

五、再进一层:高并发计数系统设计

问题:为什么不用 AtomicLong?

因为在高并发下:

  • CAS 冲突严重
  • 自旋失败率高

CAS 思维的正确做法

1. 拆状态

counter[threadId % N]

2. 本地 CAS,减少冲突

CAS(counter[i], old, old + 1)

3. 汇总时再合并

sum(counter[])

这就是 LongAdder 的工程思想


六、CAS 思维在真实系统中的落点

1. 配额 / 限流系统

  • token 数
  • 请求次数
  • 资源配额

👉 CAS 更新,失败即拒绝


2. 状态流转系统

  • 订单
  • 工单
  • 流程引擎
  • Agent 状态

👉 CAS 保证“只被处理一次”


3. 缓存更新

  • 热点 Key
  • 配置热加载
  • 单飞请求(single flight)

👉 CAS 抢“初始化权”


七、什么时候你绝对不该用 CAS?

必须明确这些边界:

  • 金融强一致事务
  • 多对象原子提交
  • 高失败成本操作
  • 高冲突热点且不可拆

CAS 是性能工具,不是信仰。


八、最终工程级总结(请背下来)

用 CAS 设计系统,不是问“能不能 CAS”,而是问:

  • 失败是不是安全的
  • 重试是不是便宜的
  • 状态是不是可拆的
  • 冲突是不是可控的

如果四个问题答案都是 ——
👉 大胆 CAS

close
arrow_upward