并发编程:从共享状态到系统协调的统一理论

并发的本质

并发系统模型

并发真正要解决的问题是:多个执行主体在不确定时间顺序下,对状态进行协作的问题。

并发系统 = 执行单元(谁在运行) + 状态(谁被修改) + 时间交错(顺序是否确定) + 协调机制(如何避免冲突)

并发的核心矛盾

共享(协同)vs隔离(避免冲突)

所有并发模型都在这两个极端之间权衡:

模型倾向
Actor隔离
CSP隔离
STM共享
线程+锁共享

并发问题分析

充分必要条件

并发问题必须同时满足三个条件:多个执行主体 + 共享可变状态 + 时间交错

消除任意一个条件:

消除项结果
不共享无竞争
不可变无修改
无时间交错无并发

并发问题不在于线程,而是共享。

错误统一抽象

并发错误本质上是协调失效

类型含义本质
安全性不做错事状态协调失效
活跃性系统持续推进进度协调失效

状态协调失效(安全性)

三个永恒问题:

问题本质根源
可见性修改是否被观察到缓存 / 重排
原子性操作是否被打断时间交错
有序性执行顺序是否一致编译器 / CPU 优化

错误本质:基于已经失效的观察结果做出了决策。

问题本质具体含义
竞态条件观察失效观察到状态为 X,基于它做决策,但在决策提交前另一个线程已把状态改成 Y,导致决策基于失效前提
脏读观察滞后读到另一个线程正在修改但尚未提交的值,这个值可能回滚或被覆盖
双重创建状态失真对象构造期间(构造函数未执行完),状态还不完整就被其他线程看到
ABA 问题时间错觉观察到的值与之前相同(A),但中间经历了 A→B→A 的完整迁移,掩盖了状态变化的历史

不变式(Invariant)是对象在生命周期中必须始终保持的业务条件,例如账户余额 >= 0、订单状态不可逆。所谓的线程安全是:无论执行顺序如何,对象始终维持其不变式(Invariant)。

线程安全 = 不变式始终成立

线程安全分类

分类含义风险来源需要同步代表
不可变安全天然线程安全无(状态不可变)不需要String、final 字段
封闭安全不共享无(执行单元私有)不需要栈变量、ThreadLocal
相对安全单操作安全复合操作被打断需要(针对单操作)AtomicInteger
组合不安全多步骤破坏不变式业务不变式未被保护需要(业务层面)多字段组合的业务对象

锁、CAS、事务都只是维护不变式的手段。

共享 + 可变 + 未受控访问 = 不安全

设计优先级:不共享 > 不可变 > 消息通信 > 乐观协调 > 互斥锁

进度协调失效(活跃性)

活跃性问题的本质是:协调结构失效导致无法推进,各方对"对方会让步"的假设循环依赖。

问题本质具体表现
死锁循环资源等待线程互相持有对方需要的锁,都阻塞等待
活锁持续协商但永不成功CPU 繁忙但系统无进展,始终在响应但无法推进
饥饿长期得不到调度机会某些线程永远无法获得所需资源

死锁:循环资源等待。Coffman 条件:互斥、占有等待、不可剥夺、循环等待。

活锁:持续协商但永不成功。特点:CPU 很高,系统无进展。

饥饿:长期得不到调度机会。典型:优先级反转、非公平锁。

内存模型:并发世界的抽象规范

内存模型是什么

内存模型是多线程环境下内存访问行为的抽象规范,回答的核心问题是:

一个线程的内存操作,何时对另一个线程可见?

它是硬件能力与编程语言之间的契约层——约定了你能期望什么,而不需要关心硬件怎么实现。

为什么需要内存模型

现代硬件为了性能做的事:

优化导致的问题
CPU 缓存线程看不到其他线程的修改(可见性问题
指令重排程序顺序与执行顺序不符(有序性问题
异步写入复合操作被打断(原子性问题

内存模型定义:当程序员写了 X 代码,真实执行时 Y 行为是否可以接受

内存模型定义三个保证

保证解决什么问题
可见性保证写入何时对线程可见(为什么他看不到我的修改)
有序性保证哪些操作顺序是强制的(重排到什么时候是错的)
原子性保证哪些操作是不可分割的(复合操作怎么保证完整)

happens-before:跨线程的保证

happens-before 是可见性 + 有序性的组合保证:

A happens-before B=B 能看到 A 的结果且A 在 B 之前排序

它不是时间概念,而是语言层(Java JMM / C++ Memory Model / Rust Send/Sync)共同遵守的理论基础。

内存模型的光谱

不同硬件的"一致性代价"不同:

类型特点代表
强顺序(TSO)代价内化,始终慢x86
弱顺序代价显式,平时快,需要时贵ARM, PowerPC
顺序一致所有线程看到一致顺序默认模型
强顺序模型:硬件帮你做所有屏障,编程简单但始终有开销弱顺序模型:需要时手动加屏障,性能更好但编程复杂

硬件到语言的链路

硬件(CPU 缓存 / 重排 / 原子指令)    ↓ 提供能力内存模型(定义契约)    ↓ 提供语法语言(Java volatile / C++ memory_order / Rust Send/Sync)    ↓程序员(使用 API)

硬件负责"能做到什么",内存模型负责"做到了什么效果"——这是必要的抽象层,让软件在不同硬件上有一致行为。

并发实践

并发控制哲学

并发控制的核心目标

并发控制不是"保护代码",而是:协调状态访问顺序。

三种核心治理哲学

策略本质代表
隔离不共享状态Actor / ThreadLocal
乐观假设冲突少CAS / MVCC
互斥同时只能一个执行者

设计优先级:隔离 > 乐观 > 互斥

因为:共享越少,复杂度越低。

悲观与乐观的本质差异

悲观并发

假设:冲突一定发生。

先限制,再执行

典型:Mutex、synchronized、数据库锁

代价:阻塞、上下文切换、死锁风险

乐观并发

假设:冲突是小概率事件。

实现方式:

乐观并发├── CAS系:先执行,失败重试(atomicInteger)└── 版本系:读写分离,快照读取(MVCC、Copy-On-Write)

代价:重试、ABA 问题、自旋消耗(针对 CAS 系)

不共享:最高级的并发安全

锁的本质:共享之后的补救。隔离:从根源消灭问题。

最好的锁 = 不存在的锁

线程封闭:对象仅属于单执行单元(栈变量、协程局部状态、Actor 内部状态),无需同步、无需可见性、无需协调。

不变性:共享不可避免时,共享不可变状态。状态永不迁移,因此不存在竞争、不需要同步、没有时间问题。

不变对象的条件:状态不可修改(消除竞争)、final 字段(禁止重排序)、构造期间不逸出(防止半初始化)。

对象生命周期与安全发布

并发问题很多不是"锁问题",而是生命周期失控问题——对象生命周期 ≠ 执行单元生命周期。

发布:对象从私有域进入共享域(放入缓存、注册监听器、返回给其他线程)。

逸出:对象在未准备好前被其他执行单元访问。最危险情况:构造期间 this 逸出,导致 final 语义失效、对象状态不完整。

安全发布的本质不是"有没有加锁",而是是否建立了 happens-before

安全发布策略:

方式本质
静态初始化类加载屏障
final 字段初始化有序性
volatile可见性
锁保护发布建立 happen-before 关系

状态机:现代并发系统的核心

并发本质上是状态协调,线程只是实现细节。真正核心是:事件 → 状态迁移 → 行为触发。

所有并发系统都可抽象为:当前状态 + 事件 → 下一个状态。

系统状态机体现
Actor消息驱动状态迁移
Reactor事件循环
工作流状态流转
Saga分布式补偿
Stream数据流状态转换

并发真正难的不是执行,而是状态一致性。状态机提供:可推导性、可验证性、可恢复性。

性能与伸缩性的本质

并发不是免费的。成本:上下文切换(调度)、缓存失效(CPU cache)、同步开销(Fence)、锁竞争(串行化)。并发目标不是线程更多,而是等待更少。

锁竞争强度:请求频率 × 持锁时间。优化核心:减少共享 > 缩短临界区 > 优化锁。

真正限制伸缩性的不是 CPU,而是共享热点。现代系统演化方向:共享最小化 + 局部性最大化。

并发模型的统一分类

第一维:通信机制

共享内存模型

我修改你直接看到

特点:

代表:

消息传递模型

我修改告诉你你再处理

特点:

代表:

第二维:安全保证来源

动态约束

运行时发现错误。

代表:

特点:

静态约束

编译期证明安全。

代表:

特点:

典型并发模型

模型本质消除的问题转移出的复杂度
锁模型控制共享数据竞争死锁
Actor封装状态共享问题消息一致性
CSPChannel 协作显式锁通道阻塞
STM内存事务锁管理回滚成本
Ownership类型隔离数据竞争生命周期复杂度

现代并发演化

Async/Await

本质:

控制流结构化。

解决:

Structured Concurrency

本质:

生命周期结构化。

目标:

任务树=生命周期树

避免:

Reactive

本质:

数据流驱动系统。

核心:

Lock-Free

本质:

消除阻塞等待。

目标:

Rust Ownership

本质:

在编译期消除共享错误。

核心思想:

共享必须显式证明安全

并发设计模式

安全性模式

模式本质
Immutable静态确定性
Thread Confinement隔离
Copy-On-Write读写分离
ThreadLocal执行单元私有化

协调性模式

模式本质
Producer-Consumer解耦
Guarded Suspension条件等待
Reader-Writer读写优化
Two-Phase Termination优雅关闭

可伸缩模式

模式本质
Worker Pool执行复用
MQ异步削峰
Sharding热点拆分
Consistent Hash稳定分布

并发测试哲学

为什么并发 Bug 难测

因为并发 Bug:

本质:

输入不仅是数据,还有时间。

测试重点

维度核心
正确性不变式
安全性不破坏状态
活跃性系统持续推进
性能延迟与吞吐

并发反模式

误区真相
并发 = 加速并发主要解决等待
锁 = 安全锁不保证业务不变式
volatile = 轻量锁不保证原子性
响应式无需同步状态共享仍然存在
协程没有并发问题协程仍有共享状态
死锁是偶然本质是设计缺陷

并发设计哲学总结

并发系统的核心矛盾

共享状态vs时间不确定性

并发演化的本质方向

整个并发技术的发展:

本质都在朝:

减少共享增强隔离提升确定性

方向演化。

并发设计的最高原则

不共享>不可变>消息通信>乐观协调>互斥锁

最终结论

并发的真正难点:

不是线程。

不是锁。

不是 API。

而是:

如何在不确定时间中维持状态一致性。

因此:

并发编程,本质上是"状态协调工程学"。

关联内容