{"name":"线程","id":"编程语言-JAVA-JAVA并发编程-线程","content":"# Java 线程\n\n> 执行单元的第一性原理（进程 / 线程 / 协程的权衡）与调度模型，见 [/操作系统/进程与线程.md](/操作系统/进程与线程.md)；并发的可见性 / 有序性见 [/编程语言/JAVA/JVM/JAVA内存模型.md](/编程语言/JAVA/JVM/JAVA内存模型.md)，控制与选型哲学见 [/编程语言/并发编程.md](/编程语言/并发编程.md)。\n>\n> 本篇聚焦 **Java 线程的独有落地**：线程模型、创建解耦、状态机、虚拟线程。一句话锚点——\n>\n> Java 线程 = 把\"可调度执行流\"这一抽象，直接映射为 OS 线程并以对象建模其状态机；虚拟线程则是把映射改写为用户态调度。\n\n## Java 线程模型的设计哲学\n\n### Java 为什么直接建模 Thread？\n\nJava 选择 Thread 作为一等公民，背后隐含了三条设计哲学：\n\n1. **拥抱操作系统调度**（而非自建调度器）\n   早期 Green Threads（M:1 自建调度）因无法用多核、一次阻塞冻结全部线程而失败；调度是全局资源分配问题，内核独占全局视野，VM 重造必劣，故退回 1:1 平台线程，白拿 SMP 与抢占。\n2. **阻塞是合法且可表达的语义**\n   1:1 映射下，内核 park 一个阻塞线程几乎免费；而同步代码顺序可读、易推理，故阻塞被设计为正常控制流而非需规避的异常态。\n3. **并发通过共享内存完成，而非消息复制**\n   对象活在共享堆、传引用不传拷贝，共享是阻力最小路径且零拷贝最快；代价是数据竞争，由此逼出 JMM 与 JUC。\n\n这决定了：\n\n* synchronized / wait / notify 的存在\n* Thread 与 OS 线程的一一映射（平台线程）\n\n平台线程（1:1）的特征、瓶颈与演进，见后文「调度模型谱系：M:1 / 1:1 / M:N」。\n\n## 线程的抽象层次结构\n\n按**稳定性**分层，决定哪些知识值得固化、哪些只需备查：\n\n| 层 | 稳定性 | 关注什么 | 详见 |\n| --- | --- | --- | --- |\n| 抽象层 | 不变 | 执行上下文 · 调度 · 阻塞与唤醒 | [/操作系统/进程与线程.md](/操作系统/进程与线程.md) |\n| JVM 实现层 | 相对稳定 | Thread 对象 · 线程状态机 · wait / park / interrupt | 本篇「线程生命周期与状态机」 |\n| 工程使用层 | 易变 | Runnable / Callable · 线程池 · 虚拟线程 | 本篇「调度模型谱系」「虚拟线程」 |\n\n## 职责拆分模型：任务 / 执行器 / 载体\n\n把\"做什么\"(任务)与\"怎么执行\"(载体)解耦成正交两维，从而让载体能演进(平台线程→虚拟线程)、能复用(线程池)、能编排(行为对象化)——而任务代码始终不变。\n\n### 核心思想：任务 → 执行器 → 载体 三元解耦\n\n解耦不是\"任务 vs 线程\"二元，而是三层各司其职——这正是 **Command + Executor** 思想的完整形态：\n\n| 角色 | 抽象 | 职责 |\n| --- | --- | --- |\n| 任务（what） | Runnable / Callable | 描述\"要做什么\" |\n| 执行器（policy） | Executor / ExecutorService | 决定\"何时、用何种策略调度\" |\n| 载体（how） | 平台线程 / 虚拟线程 | 实际承载执行的物理单元 |\n\n中间的执行器是关键：它把\"调度策略\"从\"物理载体\"中再次剥离，**线程池才有地方复用线程、虚拟线程才有 `newVirtualThreadPerTaskExecutor` 这样的替换点**。少了这一层，\"谁在复用、谁在演进\"便无处落地。\n\n### 任务维度内部：Runnable 与 Callable 之别\n\n二者都是\"做什么\"，分化点在于**能否承载结果与异常**：\n\n| | 返回值 | 受检异常 | 配对 |\n| --- | --- | --- | --- |\n| Runnable | 无 | 不能抛 | — |\n| Callable | 有 | 可抛 | Future |\n\n`Callable` 的存在，是为了让被对象化的行为能把**结果与异常经 `Future` 回传**——这正是\"行为对象化（可编排）\"的落点。\n\n## 线程生命周期与状态机（行为模型）\n\n线程是一个**有始有终、且时序不受你控制**的活动。理解它的行为，就是面对两个\"不可控\"：\n\n- **时序不可控**——状态迁移由调度器决定，无法靠\"假设事件按预期发生\"保证正确。出路是状态机：把无穷时序坍缩为有限状态，正确性建立在\"任何时刻状态都合法\"之上。\n- **起止不可强控**——你无法在任意指令点强行停止一个线程而不破坏不变量。出路是协作终止：生命周期靠中断**信号**收尾，而非 `stop()` 强杀。\n\n两者共一个原理——**并发的可控性来自约定与协作，而非强制控制**。这也是 `join` / `wait` / `interrupt` 能归并为同一套\"状态 + 协作信号\"机制的根源。\n\n### 状态即可控边界\n\n每个状态的\"出口钥匙\"握在不同角色手里——这张表本质是一张\"每个状态我有没有控制权、控制权在谁\"的杠杆图：\n\n| 状态 | 进入原因 | 出口钥匙在谁 → 你能做 / 不能做 |\n| --- | --- | --- |\n| RUNNABLE | 就绪待跑 | **调度器**决定何时上核 → 无法控制执行顺序，`优先级` / `yield` 只是建议 |\n| BLOCKED | 抢 synchronized 锁失败 | **持锁者**释放才出 → 只能改锁设计，不能直接唤醒一个 BLOCKED 线程 |\n| WAITING / TIMED_WAITING | 自身 wait / join（本质即在目标线程对象上 wait）/ park | **另一线程**发信号（notify / unpark / 中断 / 超时）→ 唤醒靠协作，不能外部强拉 |\n| TERMINATED | run 结束或异常 | **只有线程自己**能终结 → 外部只能发中断请求，`stop()` 强杀已废弃 |\n\n用错杠杆即反模式：对 RUNNABLE 用优先级猜调度 → 「编排时序」；对 TERMINATED 期待 `stop()` 强杀 → 「强制管控」（见后文反模式节）。\n\n> 中断协议、取消与生命周期管理的完整 Java 落地，见 [/编程语言/JAVA/JAVA并发编程/JAVA并发编程.md](/编程语言/JAVA/JAVA并发编程/JAVA并发编程.md)「取消、中断与生命周期管理」。\n\n## 调度模型谱系：M:1 / 1:1 / M:N\n\n### 第一性原理：调度模型 = 执行流与内核线程的映射比例\n\n线程是执行流，但执行流自己不会获得 CPU——它必须绑定到一个**内核可调度实体**（OS 线程）才能上核。于是\"如何调度线程\"只有一个自由度：\n\n> **M 个执行流 ↔ N 个内核线程的映射比例。**\n\n三种经典模型，就是这个比例的三个取值，而非三种孤立技术。\n\n| 模型  | 映射  | 调度者     | 切换成本    | 多核并行  | 一处阻塞的影响      | 代表                   |\n| --- | --- | ------- | ------- | ----- | ------------ | -------------------- |\n| M:1 | 多对一 | 运行库/VM  | 极低（用户态） | ✗ 不能  | 钉死全部执行流      | 早期 Java green thread |\n| 1:1 | 一对一 | OS 内核   | 高（陷内核）  | ✓ 天然  | 仅阻塞自身        | 平台线程（传统 Java）        |\n| M:N | 多对多 | VM + 内核 | 低（卸载）   | ✓（N 核） | 卸载让出 carrier | 虚拟线程                 |\n\n### 演进逻辑：每次跃迁由当时的主导约束决定\n\n**M:1 → 1:1：被\"多核 + 阻塞\"淘汰**\n\n早期 OS 线程昂贵甚至缺位，运行时自建调度器（M:1）是务实选择。但它有两个致命伤：\n\n* 所有执行流共享一个内核线程 → **无法利用多核**\n* 任一阻塞系统调用钉死底层唯一内核线程 → **一处阻塞，全员停摆**\n\n当多核普及、OS 线程成熟，1:1 用\"内核统一调度\"一次性解决这两点，于是胜出——代价是**线程沦为稀缺资源、阻塞即浪费一个 OS 线程**（平台线程瓶颈）。\n\n**1:1 → M:N：续体让 M:1 的两个致命伤同时消失**\n\nM:N 不是回到 M:1，而是**螺旋上升**——它保留 M:1 的\"用户态廉价\"，又补上 M:1 缺的两件事：\n\n* 阻塞时**卸载**（unmount）而非钉死 → 解决\"一处阻塞全员停摆\"\n* M 个虚拟线程跑在 **N 个 carrier**（N≈核数，ForkJoinPool 工作窃取）→ 解决\"无法多核\"\n\n使\"卸载\"成为可能的前提，是 GC 堆 + 续体让线程栈可挂起/搬迁。这一前提在 green thread 时代并不具备，这正是 M:N \"现在才可行\"的根因。\n\n由此，用户态执行上下文可正交拆为两件独立的事——这是协程 / 纤程的统一抽象：\n\n| 构件 | 职责 |\n| --- | --- |\n| Continuation（续体） | 保存与恢复执行过程（栈、PC），使一段计算可在任意点挂起并稍后续跑 |\n| Scheduler（调度器） | 决定续体何时、在哪个 carrier 上恢复运行 |\n\n二者解耦的意义：续体只管\"能不能停下再续\"，调度器只管\"何时续\"——更换调度策略无需改动业务代码。这也解释了为何 M:N 的核心不是\"更快\"，而是**用更低成本管理更大规模的并发**。\n\n故虚拟线程并非全新发明，而是 green thread（同为运行时调度的执行上下文）在 M:N + 续体成熟后的**重做**。\n\n### 不变规律\n\n三代演进从未消除权衡，只是不断转移代价：\n\n> **切换成本 × 多核并行 × 阻塞语义 × 资源稀缺——四者不可同时最优。**\n\n* M:1 押注切换成本，输在多核与阻塞\n* 1:1 押注多核与阻塞隔离，输在资源稀缺\n* M:N 用 **JVM 实现复杂度** 换回前三者——没有银弹，只是把代价压进了运行时\n\n## 虚拟线程：并发模型的范式升级\n\n它的本质与机制（用户态调度的执行上下文、阻塞即卸载、M:N carrier）已在上节谱系给出——其意义是让**「阻塞语义」与「资源利用率」这对长期冲突首次同时满足**。本节只回答谱系推不出的两件事：它在并发模型全景中的**位置**，以及**何时该用、何时不该用**。\n\n### 虚拟线程在并发模型中的位置\n\n| 模型      | 核心思想        | 代价        |\n| ------- | ----------- | --------- |\n| 线程池     | 控制线程数量      | 编程复杂度     |\n| Reactor | 非阻塞 IO      | 心智负担      |\n| 虚拟线程    | 阻塞语义 + 协程调度 | JVM 实现复杂度 |\n\n> **虚拟线程降低了并发编程的门槛，而非替代所有模型。**\n\n三者代价的差别在归属：线程池与 Reactor 把复杂度压给使用者，虚拟线程独家把它压给 JVM 实现——这才是它\"降低门槛\"的真正机制：代价转移，而非代价消失。\n\n### 执行模型的选型边界\n\n模型不创造资源，只改变\"线程\"这一资源的分配效率。因此选型只有一个决定性问题：\n\n> **当前并发的瓶颈，是线程数、CPU 算力，还是下游容量？**\n\n模型只能解除\"线程数\"这一种瓶颈；另外两种，换任何模型都无效。\n\n| 瓶颈 / 负载         | 该用              | 不该用             | 原因                          |\n| -------------- | --------------- | --------------- | --------------------------- |\n| IO-bound 高并发（>数千） | 虚拟线程            | 池化平台线程（并发被池封顶）  | 瓶颈 = 线程稀缺，虚拟线程正好解除          |\n| CPU-bound 计算密集 | 平台线程池（≈核数）      | 虚拟线程（无收益）       | 上限是核数；虚拟线程提供 scale，不提供 speed |\n| 下游有硬上限（DB/外部限流） | 信号量 / 连接池显式背压   | \"无限线程\"的幻觉       | 瓶颈在下游；海量虚拟线程把全量压力打到下游，放大冲击   |\n| 低并发（<数千）       | 任意，简单优先         | 为虚拟线程徒增认知负担     | 收益不显著                       |\n\n> **虚拟线程解除的是\"线程稀缺\"这一种封顶。瓶颈不在线程数时，它既不提速，也不该用。**\n\n## 统一视角下的线程哲学\n\n### 五条核心信条\n\n1. Thread 是执行载体，不承载业务\n2. 顺序通过同步表达，不靠 Thread API 编排\n3. 阻塞是合法的\n4. 中断是协作的\n5. 性能来自模型选择，而非 API 技巧\n\n> **并发能力 = 执行模型 × 调度策略 × 资源成本**。\n\n### 展望：廉价线程使能的新范式\n\n虚拟线程把线程从稀缺资源变为廉价资源，由此打开更高层抽象——**结构化并发**：任务生命周期跟随代码块，作用域结束自动级联取消与等待。完整 Java 落地见 [/编程语言/JAVA/JAVA并发编程/JAVA并发编程.md](/编程语言/JAVA/JAVA并发编程/JAVA并发编程.md)「取消、中断与生命周期管理」。\n\n## 线程认知的反模式\n\n线程的反模式几乎都源于同一个根错误——**把\"执行流\"误当成了它不是的东西**。五条误区，正是前述五条核心信条各自的反面：\n\n### 反模式一：业务化线程\n\n违反信条 1「Thread 是执行载体，不承载业务」——误当**业务对象**。\n\n* 信号：继承 `Thread` 承载业务逻辑；用\"线程\"指代\"任务\"；线程身份等同业务身份\n* 根因：混淆\"如何被调度\"（线程）与\"要做什么\"（任务）。二者正交\n* 对策：任务用 Runnable/Callable 表达，调度交给执行器（Command + Executor）。线程是载体，可换、可池、可虚拟化\n* 可迁移：执行流与任务正交，任何运行时都应解耦 what 与 how\n\n### 反模式二：编排时序\n\n违反信条 2「顺序通过同步表达」——误当**可精确排序的指令流**。\n\n* 信号：用 `sleep` / `yield` / 优先级猜调度；靠\"先启动先执行\"的时序假设保证正确性\n* 根因：抢占式调度下状态迁移时机由内核与同步原语共同决定，不可被 API 精确控制\n* 对策：用同步原语表达依赖、用并发工具类表达约束——把顺序写成**语义**，而非赌调度结果\n* 可迁移：顺序是同步语义，不是调度巧合\n\n### 反模式三：消灭阻塞\n\n违反信条 3「阻塞是合法的」——误当**必须根除的负担**。\n\n* 信号：为规避阻塞无脑改异步/回调，牺牲可读性与可调试性\n* 根因：阻塞的代价不在阻塞本身，而在**阻塞所绑定资源的稀缺**。1:1 模型下阻塞 = 占死一个 OS 线程，才是真问题\n* 对策：让阻塞变廉价（M:N 卸载），而非消灭阻塞。同步代码的可读性是资产，不该为伸缩性轻易放弃\n* 可迁移：该降低的是阻塞的**资源成本**，不是阻塞本身\n\n### 反模式四：强制管控\n\n违反信条 4「中断是协作的」——误当**可抢占强杀的傀儡**。\n\n* 信号：期待 `stop()` 强杀线程；忽略中断信号；用普通共享变量当停止标志却不保证可见性\n* 根因：并发安全来自协作协议，强制终止会在任意指令边界破坏不变量\n* 对策：中断/取消走协作协议（响应 `interrupt`、用 `volatile`/原子量传递停止意图），让被中断方在安全点自行退出\n* 可迁移：生命周期靠协议，不靠强制\n\n### 反模式五：无界线程\n\n违反信条 5「性能来自模型选择」——误当**零成本资源**。\n\n* 信号：相信\"线程越多越快\"；无界创建线程；把池大小当性能调参的主轴\n* 根因：线程是有成本的资源（栈、调度、上下文切换）。并发度上限由资源成本与执行模型决定，不由线程数主观设定\n* 对策：先选模型（池化 / Reactor / 虚拟线程），再让资源成本决定并发度；用信号量限的是**下游资源**，不是线程本身\n* 可迁移：并发度由资源成本 × 执行模型决定，非线程数主观设定\n\n## 总结\n\n> **线程只是\"被调度的执行流\"。全篇内容，都是围绕这个最小定义的三次展开与一类误用。**\n\n- **是什么 + 怎么拆**：执行流与任务正交，故有「任务 / 执行器 / 载体」三元解耦——载体得以从平台线程演进到虚拟线程，而任务代码不变。\n- **怎么行为 + 怎么控**：执行流的行为是一台状态机，出口钥匙分握在调度器、锁、其他线程手中——正确性靠\"约定 + 协作\"，而非强制编排。\n- **怎么映射 + 怎么演进**：调度的唯一自由度是「M 个执行流 ↔ N 个内核线程」的比例；M:1 → 1:1 → M:N 三代从未消除权衡，只是不断转移代价——没有银弹。\n\n五类反模式，无一不是背离了这个最小定义；纠偏方向只有一个——**把线程看回它本来的样子：一条被调度的执行流，不多也不少。**\n\n## 关联内容（自动生成）\n\n- [/编程语言/JAVA/JAVA并发编程/JAVA并发编程.md](/编程语言/JAVA/JAVA并发编程/JAVA并发编程.md) 本篇将取消/中断/生命周期管理、结构化并发的完整落地委派给它（正文 3 处前向引用）\n- [/编程语言/JAVA/JAVA并发编程/线程池.md](/编程语言/JAVA/JAVA并发编程/线程池.md) 「执行器/载体」中线程复用的工程落地，对应职责拆分与选型节\n- [/编程语言/JAVA/JAVA并发编程/并发工具类.md](/编程语言/JAVA/JAVA并发编程/并发工具类.md) 同步原语与背压（信号量/连接池）的来源，对应\"用同步表达依赖\"\n- [/编程语言/JAVA/JAVA并发编程/并发集合.md](/编程语言/JAVA/JAVA并发编程/并发集合.md) 设计哲学③共享内存模型下的线程安全数据结构（边缘关联）\n- [/编程语言/并发模型.md](/编程语言/并发模型.md) Reactor/Actor 等模型，与本篇 M:1/1:1/M:N 谱系及虚拟线程定位形成横向对比\n- [/操作系统/进程与线程.md](/操作系统/进程与线程.md) 进程/线程/协程第一性原理与调度模型的上游，本篇多处依赖\n- [/编程语言/JAVA/JVM/JAVA内存模型.md](/编程语言/JAVA/JVM/JAVA内存模型.md) 设计哲学③\"共享内存\"的代价兑现：JMM 规定可见性/有序性\n- [/操作系统/死锁.md](/操作系统/死锁.md) 死锁是线程竞争共享锁的典型故障，与 BLOCKED 状态、强制管控反模式直接相关\n\n","metadata":"tags: ['并发编程', '操作系统', '编程语言']","hasMoreCommit":false,"totalCommits":10,"commitList":[{"date":"2026-06-25T21:22:50+08:00","author":"MY","message":"refactor(java并发): 退役 基础概念.md，三处增量迁入权威篇","hash":"cc9e3a8038d179371125a1151bb670322796c083"},{"date":"2026-06-25T21:04:18+08:00","author":"MY","message":"feat(java): 添加结构化并发和虚拟线程相关内容","hash":"5be69e5da0f7fe3306625af86b67f3fbee22d588"},{"date":"2026-02-12T14:07:03+08:00","author":"MY","message":"doc: 整理标签","hash":"290b3e8ad18f48832ac282290238d020fc030a88"},{"date":"2026-01-08T16:36:52+08:00","author":"MY","message":"docs(java-thread): 重构Java线程文档结构并完善内容","hash":"a7b0dfe9ed0806d1ad9f39f878bf79804c57d616"},{"date":"2024-11-19T15:26:31+08:00","author":"MY","message":"📦Java 并发编程","hash":"63d98d49e4151c9530896a7a8c1bd0cdc4d9a762"},{"date":"2024-11-01T17:09:50+08:00","author":"MY","message":"✏线程 API 之间的关系图","hash":"0f26bd0cb7ba25c89ec28aa1dd76c356ca3a1ccf"},{"date":"2023-09-20T17:13:50+08:00","author":"MY","message":"✏线程","hash":"15fea6ddb96ad6935376a992804262c53567cdf1"},{"date":"2022-10-19T21:32:18+08:00","author":"MY","message":"✏️Java并发编程","hash":"5ec012b5f65756ede6a0402ff1eb8eba6b398a9e"},{"date":"2022-03-09T17:46:16+08:00","author":"cjiping","message":"✏️更新 JAVA并发编程","hash":"05cdd8647ade510badf68feae84d459e83c2b943"},{"date":"2021-09-09T16:04:56+08:00","author":"cjiping","message":"📦整理 Java 线程","hash":"38a99898a4b35a1db75c1e0e87df2defb5ce7525"}],"createTime":"2021-09-09T16:04:56+08:00"}