本文是承接上一篇 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