Java 并发编程

并发编程的本质问题

并发编程是关于在不确定时序下,通过某种协调机制达成正确行为的问题。

并发的三个永恒问题

大多数并发 Bug,本质上都可以归结为以下三类之一:

  1. 可见性(Visibility)一个线程对状态的修改,是否能被其他线程观察到。

  2. 原子性(Atomicity)一个操作是否不可被中断、不可被拆分。

  3. 有序性(Ordering)程序执行顺序在不同线程观察下是否一致。

可见性由内存屏障(实现 Happens-Before)保证,原子性由锁/CAS 保证,有序性由重排约束(满足 Happens-Before)保证

并发解决方案的层次结构

应用层:并发设计模式        ↓抽象层:Lock、Semaphore、Barrier        ↓基础设施层:等待-通知(Object.wait/notify, Condition.await/signal)        ↓硬件层:内存屏障、CPU 缓存一致性

并发控制的核心思想

并发控制的核心是管理协调的不确定性,共享是协调的一种形式。

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

并发问题的根源在于共享,因此:

不共享 = 天然线程安全

线程封闭

常见形式:

线程封闭的本质是用"隔离"换"安全",代价是数据交换和内存开销。只有在线程间无共享需求时,线程封闭才是最优解

只读共享:不变性

如果共享不可避免,下一优先级是:

共享但不可变

不变对象的并发安全来自其数学属性,而非同步机制:

不变性的三个条件:

不变性是并发世界中最强的确定性来源。

受控共享:同步与协作

当对象既需要共享,又必须可变:

并发的核心任务 = 协调对状态的访问顺序

这引出了同步、锁、条件等待等机制。

对象共享与发布模型

对象生命周期视角

并发安全问题,本质是对象生命周期与线程生命周期不一致

关键问题:

发布与逸出

构造期间逸出是最危险的并发错误之一。

安全发布

安全发布不是”是否加锁”,而是建立可见性与有序性保证

通用安全发布策略:

没有安全发布,线程安全无从谈起。

并发类的设计模式

实例封闭

将线程不安全对象封装在一个受控的并发边界内。

这是最常见、也是最稳健的并发类设计方式。

线程安全委托

将并发安全责任交给更底层的线程安全组件。

前提:

委托失败,往往源于”多个原子操作组合后不再原子”。

状态依赖操作

并发系统中最复杂的不是互斥,而是:

操作是否依赖于状态是否满足某个条件

这类操作需要:

并发系统的活跃性困境

死锁(Deadlock)

线程循环等待资源,程序永久停滞。

Coffman 四条件:互斥 | 占有且等待 | 不可剥夺 | 循环等待

预防:固定加锁顺序、一次性申请、超时放弃(tryLock)、缩小临界区。

活锁(Livelock)

线程持续行动但无进展,消耗 CPU 但空转。

区别死锁活锁
CPU 消耗
自愈可能不能可能

预防:引入随机延迟、限制重试次数、引入协调者。

饥饿(Starvation)

线程长期无法获得资源,但最终能获得(与死锁"永远不能"不同)。

预防:公平锁(ReentrantLock(true))、避免优先级反转、动态优先级提升。


预防优于检测,设计优于补救。

核心原则:不共享 > 高层工具 > 最小化锁粒度 > 避免嵌套锁循环依赖。

等待、通知与协作机制

等待-通知的本质模型

等待不是”睡眠”,而是:

在条件未满足时,不用浪费 CPU 空转,主动让出执行权,并等待条件变化的通知

核心原则:

条件队列与显式条件

条件队列解决的是:

它体现的是状态机式并发设计思想

取消、关闭与线程生命周期管理

取消不是强制终止

并发系统中:

取消是一种协作协议,而非控制命令

线程必须自行决定:

中断的语义

中断不是异常,而是:

一种跨线程的协作信号

设计原则:

基于任务的取消

Future、Executor 的意义在于:

性能与伸缩性的并发视角

并发的成本模型

并发不是免费的:

并发的目标不是”线程更多”,而是”等待更少”。并发的开销可能抵消并发带来的收益。

锁竞争的本质

竞争强度取决于:

优化方向:

当竞争强度较大时,增加线程数并不能带来性能提升。

JVM 层面的并发优化

这些优化的前提是:

并发设计本身是合理的

并发程序的测试哲学

并发测试的困难

并发 Bug 是:

测试关注点

并发编程反模式与误区

并发错误的根源往往不是语法或API的误用,而是认知框架的偏差

核心原则

最小共享原则:不共享最安全。共享范围越小、频率越低,出现并发问题的概率越小。

不可变优先原则:状态不可变则无需同步。优先设计不可变对象,而非为可变对象添加锁。

角色清晰原则:明确谁拥有锁、谁等待锁、谁释放锁。死锁几乎总是角色定义模糊的系统性缺陷,而非运气不好。

常见认知陷阱

误区真相
并发=加速并发解决资源利用率和响应延迟,非无条件加速
加锁=安全锁只提供互斥与可见性,不保证业务逻辑原子性
volatile=轻量锁volatile保证可见性和有序性,不保证原子性
响应式无需同步响应式解决调用栈阻塞,状态共享仍需同步
死锁是运气不好死锁是锁设计缺陷的表征,破坏四要素之一可预防

关联内容(自动生成)