Virtual Threads、Scoped Values 与 Structured Concurrency
虚拟线程适合用来理解一个新问题:大量任务在等待 I/O 时,怎样降低线程带来的负担?它不负责让计算本身变快。读这一章时,把“同时等多少任务”与“同时算多少任务”分开,再看上下文传递和子任务管理。
版本与资料依据
last_verified:2026-08-24version_scope:Virtual Threads stable since Java 21; Scoped Values final in JDK 25; Structured Concurrency sixth preview in JDK 26source_type:OpenJDK JEPstability:version-sensitive
从一次页面请求理解三者的用处
假设一个页面要同时加载用户资料和订单。虚拟线程回答的是“这些任务等待网络时,怎样少占用昂贵的平台线程”;Scoped Values 回答的是“这一段调用链怎样读到同一个请求编号”;结构化并发回答的是“资料查询失败以后,订单查询由谁等待、取消和收尾”。它们处理的是三件事,并不是三个不同名字的线程池。
使用虚拟线程时,一个任务一个虚拟线程通常比自己维护虚拟线程池更合适,但数据库连接数量仍有限。若只有 20 个连接,创建一万个虚拟线程也不会让数据库同时处理一万个查询。需要结合连接池、超时和并发上限控制实际负载。
结构化并发的价值在于让“任务已经结束”有明确含义:返回父任务结果之前,处理好子任务的完成和失败。取消是协作行为,外部操作还要支持中断或超时;发出取消信号,不等于数据库写入已回滚。
版本补充(2026-09-20):JDK 27中的 Structured Concurrency 是 JEP 533 第七次预览。下面继续保留 JDK 26 第六次预览示例,不宣称它已按 27 编译验证;稳定生产代码与预览实验要分开构建。
1. 四种不同概念
| 概念 | 抽象 | 解决的问题 |
|---|---|---|
| Platform thread | OS 调度线程的 Java 表示 | 通用并发执行 |
| Virtual thread | JVM 调度的轻量 Thread | 大量阻塞型任务的 thread-per-task 成本 |
| Async programming | 以 future/callback 表达稍后完成 | 避免调用线程等待、组合完成事件 |
| Reactive programming | stream + non-blocking + demand/backpressure | 端到端异步数据流和资源控制 |
Virtual Thread 不保证单个请求更快;收益来自能以同步风格承载更多并发阻塞任务。CPU-bound 工作仍受核心数约束。监测连接池、外部 API 和锁等真实瓶颈。
2. Scoped Values(JDK 25)
用于在有界调用期间共享不可变/只读式上下文,尤其适合 request identity 或 trace context。它不是全局变量,也不替代显式业务参数。
static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
ScopedValue.where(REQUEST_ID, "req-42").run(() -> service());
共享对象本身若可变,仍需同步。不要将 secret 或大型对象无界传播。
3. Structured Concurrency(JDK 26 Sixth Preview)
其目标是让父任务与子任务生命周期形成词法结构,统一 join、失败与取消。由于是 Preview,示例需使用 --enable-preview,API 可在后续版本继续变化。
// JDK 26 preview:以当期 JEP/API 为准
try (var scope = StructuredTaskScope.open()) {
var user = scope.fork(this::loadUser);
var order = scope.fork(this::loadOrder);
scope.join();
return new Result(user.get(), order.get());
}
4. 验证
模拟一个子任务失败、deadline、父任务取消与 scoped binding;确认兄弟任务处理、异常传播、资源关闭和 thread dump/JFR 可观察性。生产基线若禁用 preview,使用稳定 Executor/Future 设计并把迁移封装在边界内。