Java 线程

执行单元的第一性原理(进程 / 线程 / 协程的权衡)与调度模型,见 /操作系统/进程与线程.html;并发的可见性 / 有序性见 /编程语言/JAVA/JVM/JAVA内存模型.html,控制与选型哲学见 /编程语言/并发编程.html

本篇聚焦 Java 线程的独有落地:线程模型、创建解耦、状态机、虚拟线程。一句话锚点——

Java 线程 = 把"可调度执行流"这一抽象,直接映射为 OS 线程并以对象建模其状态机;虚拟线程则是把映射改写为用户态调度。

Java 线程模型的设计哲学

Java 为什么直接建模 Thread?

Java 选择 Thread 作为一等公民,背后隐含了三条设计哲学:

  1. **拥抱操作系统调度**(而非自建调度器) 早期 Green Threads(M:1 自建调度)因无法用多核、一次阻塞冻结全部线程而失败;调度是全局资源分配问题,内核独占全局视野,VM 重造必劣,故退回 1:1 平台线程,白拿 SMP 与抢占。
  2. **阻塞是合法且可表达的语义** 1:1 映射下,内核 park 一个阻塞线程几乎免费;而同步代码顺序可读、易推理,故阻塞被设计为正常控制流而非需规避的异常态。
  3. **并发通过共享内存完成,而非消息复制** 对象活在共享堆、传引用不传拷贝,共享是阻力最小路径且零拷贝最快;代价是数据竞争,由此逼出 JMM 与 JUC。

这决定了:

平台线程(1:1)的特征、瓶颈与演进,见后文「调度模型谱系:M:1 / 1:1 / M:N」。

线程的抽象层次结构

稳定性分层,决定哪些知识值得固化、哪些只需备查:

稳定性 关注什么 详见
抽象层 不变 执行上下文 · 调度 · 阻塞与唤醒 /操作系统/进程与线程.html
JVM 实现层 相对稳定 Thread 对象 · 线程状态机 · wait / park / interrupt 本篇「线程生命周期与状态机」
工程使用层 易变 Runnable / Callable · 线程池 · 虚拟线程 本篇「调度模型谱系」「虚拟线程」

职责拆分模型:任务 / 执行器 / 载体

把"做什么"(任务)与"怎么执行"(载体)解耦成正交两维,从而让载体能演进(平台线程→虚拟线程)、能复用(线程池)、能编排(行为对象化)——而任务代码始终不变。

核心思想:任务 → 执行器 → 载体 三元解耦

解耦不是"任务 vs 线程"二元,而是三层各司其职——这正是 Command + Executor 思想的完整形态:

角色 抽象 职责
任务(what) Runnable / Callable 描述"要做什么"
执行器(policy) Executor / ExecutorService 决定"何时、用何种策略调度"
载体(how) 平台线程 / 虚拟线程 实际承载执行的物理单元

中间的执行器是关键:它把"调度策略"从"物理载体"中再次剥离,线程池才有地方复用线程、虚拟线程才有 newVirtualThreadPerTaskExecutor 这样的替换点。少了这一层,"谁在复用、谁在演进"便无处落地。

任务维度内部:Runnable 与 Callable 之别

二者都是"做什么",分化点在于能否承载结果与异常

返回值 受检异常 配对
Runnable 不能抛
Callable 可抛 Future

Callable 的存在,是为了让被对象化的行为能把结果与异常经 Future 回传——这正是"行为对象化(可编排)"的落点。

线程生命周期与状态机(行为模型)

线程是一个有始有终、且时序不受你控制的活动。理解它的行为,就是面对两个"不可控":

两者共一个原理——并发的可控性来自约定与协作,而非强制控制。这也是 join / wait / interrupt 能归并为同一套"状态 + 协作信号"机制的根源。

状态即可控边界

每个状态的"出口钥匙"握在不同角色手里——这张表本质是一张"每个状态我有没有控制权、控制权在谁"的杠杆图:

状态 进入原因 出口钥匙在谁 → 你能做 / 不能做
RUNNABLE 就绪待跑 调度器决定何时上核 → 无法控制执行顺序,优先级 / yield 只是建议
BLOCKED 抢 synchronized 锁失败 持锁者释放才出 → 只能改锁设计,不能直接唤醒一个 BLOCKED 线程
WAITING / TIMED_WAITING 自身 wait / join(本质即在目标线程对象上 wait)/ park 另一线程发信号(notify / unpark / 中断 / 超时)→ 唤醒靠协作,不能外部强拉
TERMINATED run 结束或异常 只有线程自己能终结 → 外部只能发中断请求,stop() 强杀已废弃

用错杠杆即反模式:对 RUNNABLE 用优先级猜调度 → 「编排时序」;对 TERMINATED 期待 stop() 强杀 → 「强制管控」(见后文反模式节)。

中断协议、取消与生命周期管理的完整 Java 落地,见 /编程语言/JAVA/JAVA并发编程/JAVA并发编程.html「取消、中断与生命周期管理」。

调度模型谱系:M:1 / 1:1 / M:N

第一性原理:调度模型 = 执行流与内核线程的映射比例

线程是执行流,但执行流自己不会获得 CPU——它必须绑定到一个内核可调度实体(OS 线程)才能上核。于是"如何调度线程"只有一个自由度:

M 个执行流 ↔ N 个内核线程的映射比例。

三种经典模型,就是这个比例的三个取值,而非三种孤立技术。

模型 映射 调度者 切换成本 多核并行 一处阻塞的影响 代表
M:1 多对一 运行库/VM 极低(用户态) ✗ 不能 钉死全部执行流 早期 Java green thread
1:1 一对一 OS 内核 高(陷内核) ✓ 天然 仅阻塞自身 平台线程(传统 Java)
M:N 多对多 VM + 内核 低(卸载) ✓(N 核) 卸载让出 carrier 虚拟线程

演进逻辑:每次跃迁由当时的主导约束决定

M:1 → 1:1:被"多核 + 阻塞"淘汰

早期 OS 线程昂贵甚至缺位,运行时自建调度器(M:1)是务实选择。但它有两个致命伤:

当多核普及、OS 线程成熟,1:1 用"内核统一调度"一次性解决这两点,于是胜出——代价是线程沦为稀缺资源、阻塞即浪费一个 OS 线程(平台线程瓶颈)。

1:1 → M:N:续体让 M:1 的两个致命伤同时消失

M:N 不是回到 M:1,而是螺旋上升——它保留 M:1 的"用户态廉价",又补上 M:1 缺的两件事:

使"卸载"成为可能的前提,是 GC 堆 + 续体让线程栈可挂起/搬迁。这一前提在 green thread 时代并不具备,这正是 M:N "现在才可行"的根因。

由此,用户态执行上下文可正交拆为两件独立的事——这是协程 / 纤程的统一抽象:

构件 职责
Continuation(续体) 保存与恢复执行过程(栈、PC),使一段计算可在任意点挂起并稍后续跑
Scheduler(调度器) 决定续体何时、在哪个 carrier 上恢复运行

二者解耦的意义:续体只管"能不能停下再续",调度器只管"何时续"——更换调度策略无需改动业务代码。这也解释了为何 M:N 的核心不是"更快",而是用更低成本管理更大规模的并发

故虚拟线程并非全新发明,而是 green thread(同为运行时调度的执行上下文)在 M:N + 续体成熟后的重做

不变规律

三代演进从未消除权衡,只是不断转移代价:

切换成本 × 多核并行 × 阻塞语义 × 资源稀缺——四者不可同时最优。

虚拟线程:并发模型的范式升级

它的本质与机制(用户态调度的执行上下文、阻塞即卸载、M:N carrier)已在上节谱系给出——其意义是让**「阻塞语义」与「资源利用率」这对长期冲突首次同时满足**。本节只回答谱系推不出的两件事:它在并发模型全景中的位置,以及何时该用、何时不该用

虚拟线程在并发模型中的位置

模型 核心思想 代价
线程池 控制线程数量 编程复杂度
Reactor 非阻塞 IO 心智负担
虚拟线程 阻塞语义 + 协程调度 JVM 实现复杂度

虚拟线程降低了并发编程的门槛,而非替代所有模型。

三者代价的差别在归属:线程池与 Reactor 把复杂度压给使用者,虚拟线程独家把它压给 JVM 实现——这才是它"降低门槛"的真正机制:代价转移,而非代价消失。

执行模型的选型边界

模型不创造资源,只改变"线程"这一资源的分配效率。因此选型只有一个决定性问题:

当前并发的瓶颈,是线程数、CPU 算力,还是下游容量?

模型只能解除"线程数"这一种瓶颈;另外两种,换任何模型都无效。

瓶颈 / 负载 该用 不该用 原因
IO-bound 高并发(>数千) 虚拟线程 池化平台线程(并发被池封顶) 瓶颈 = 线程稀缺,虚拟线程正好解除
CPU-bound 计算密集 平台线程池(≈核数) 虚拟线程(无收益) 上限是核数;虚拟线程提供 scale,不提供 speed
下游有硬上限(DB/外部限流) 信号量 / 连接池显式背压 "无限线程"的幻觉 瓶颈在下游;海量虚拟线程把全量压力打到下游,放大冲击
低并发(<数千) 任意,简单优先 为虚拟线程徒增认知负担 收益不显著

虚拟线程解除的是"线程稀缺"这一种封顶。瓶颈不在线程数时,它既不提速,也不该用。

统一视角下的线程哲学

五条核心信条

  1. Thread 是执行载体,不承载业务
  2. 顺序通过同步表达,不靠 Thread API 编排
  3. 阻塞是合法的
  4. 中断是协作的
  5. 性能来自模型选择,而非 API 技巧

并发能力 = 执行模型 × 调度策略 × 资源成本

展望:廉价线程使能的新范式

虚拟线程把线程从稀缺资源变为廉价资源,由此打开更高层抽象——结构化并发:任务生命周期跟随代码块,作用域结束自动级联取消与等待。完整 Java 落地见 /编程语言/JAVA/JAVA并发编程/JAVA并发编程.html「取消、中断与生命周期管理」。

线程认知的反模式

线程的反模式几乎都源于同一个根错误——把"执行流"误当成了它不是的东西。五条误区,正是前述五条核心信条各自的反面:

反模式一:业务化线程

违反信条 1「Thread 是执行载体,不承载业务」——误当业务对象

反模式二:编排时序

违反信条 2「顺序通过同步表达」——误当可精确排序的指令流

反模式三:消灭阻塞

违反信条 3「阻塞是合法的」——误当必须根除的负担

反模式四:强制管控

违反信条 4「中断是协作的」——误当可抢占强杀的傀儡

反模式五:无界线程

违反信条 5「性能来自模型选择」——误当零成本资源

总结

线程只是"被调度的执行流"。全篇内容,都是围绕这个最小定义的三次展开与一类误用。

五类反模式,无一不是背离了这个最小定义;纠偏方向只有一个——把线程看回它本来的样子:一条被调度的执行流,不多也不少。

关联内容(自动生成)