Scoped Values 与结构化并发

共 20 题
📑 题目列表 20 题
#
★★★

1. Structured Concurrency 与 CompletableFuture 的边界

Structured Concurrency 与 CompletableFuture 的边界是什么?

  • 结构化并发的生命周期管理
  • CompletableFuture 的异步回调
  • 两者的适用场景

Structured Concurrency(StructuredTaskScope)与 CompletableFuture 的边界:结构化并发强调"任务有明确生命周期、父子结构、异常与取消确定性传播",适合并发任务编排(并行子任务、聚合结果);CompletableFuture 强调"异步回调、结果组合",适合无明确结构的异步流水线。结构化并发的优势是:子任务在同一作用域内,谁也不会逃逸,异常统一处理,取消可传播;CompletableFuture 的回调链更灵活但无结构保证,任务可能逃逸或异常泄漏。适用选择:并发"一批任务→聚合"用结构化并发;独立的异步数据处理链用 CompletableFuture。

结构化并发提供"结构"与确定性,CompletableFuture 提供"灵活"与组合。边界在于是否需要父子生命周期与取消保证——需要则用结构化并发,只需异步组合则用 CompletableFuture。

#
★★★

2. Scoped Values 与 ThreadLocal 的差异

Scoped Values 与 ThreadLocal 的差异是什么?

  • 可变性与生命周期
  • 继承与逃逸
  • 性能与安全

Scoped Values 与 ThreadLocal 的差异:1)可变性:ThreadLocal 可变,Scoped Values 不可变(绑定后只读);2)生命周期:ThreadLocal 依赖线程生命周期,易泄漏,Scoped Values 有明确作用域(run 方法内),作用域结束自动清理;3)继承:ThreadLocal 有 InheritableThreadLocal 但易传播混乱,Scoped Values 通过结构化并发(StructuredTaskScope.fork)明确继承,不逃逸到无关线程;4)性能:Scoped Values 在虚拟线程下更高效(避免每线程哈希表),且更安全(不可变、无泄漏)。官方推荐用 Scoped Values 替代 ThreadLocal。

Scoped Values 是针对 ThreadLocal 缺陷的演进:不可变 + 明确作用域 + 结构化继承。在虚拟线程与结构化并发下,Scoped Values 是传输上下文的首选。

#
★★★

3. Structured Concurrency 的 InheritableThreadLocals 迁移路径

Structured Concurrency 下 InheritableThreadLocals 的迁移路径是什么?

  • InheritableThreadLocal 的问题
  • 迁移到 Scoped Values
  • 迁移步骤

InheritableThreadLocal 在子线程创建时复制父线程的值,但复制是一次性的、不可控的,在虚拟线程与结构化并发下会导致上下文泄漏与混乱(虚拟线程数量巨大,复制量大且易错)。迁移路径:1)用 Scoped Value 替代 InheritableThreadLocal,通过 ScopedValue.where(key, value).run(...) 绑定值,在结构化并发(StructuredTaskScope.fork)中自动继承给子任务;2)将 ThreadLocal 的读写点改为 ScopedValue 的 get,绑定点改为 where/run;3)移除对 InheritableThreadLocal 的依赖与手工复制逻辑。迁移后上下文传递明确、无泄漏、性能更好。

InheritableThreadLocal 的"创建时复制"语义在虚拟线程下是反模式。迁移到 Scoped Values 让上下文按结构化作用域明确传递,是官方推荐的路径。

#
★★★

4. StructuredTaskScope 的 ShutdownOnFailure/ShutdownOnSuccess

StructuredTaskScope 的 ShutdownOnFailure/ShutdownOnSuccess 策略是什么?

  • ShutdownOnFailure 语义
  • ShutdownOnSuccess 语义
  • 取消与结果收集

StructuredTaskScope 提供两个策略:ShutdownOnFailure——任一子任务失败时关闭整个作用域,取消其余子任务,适合"全成功才算成功"(如并发查询,任一失败则整体失败);ShutdownOnSuccess——任一子任务成功时关闭作用域,取消其余,适合"有一个成功即可"(如优先服务,第一个成功就返回)。两者都通过 fork 子任务、await 等待、join 收集结果。ShutdownOnFailure 在失败时可通过异常传播触发整体失败;ShutdownOnSuccess 在首个成功时返回结果。关闭后调用 scope.join() 或 result() 收集。

这两个策略是实现"失败快速"与"成功快速"的并发聚合语义。它们让结构化并发在"先到先得"与"全有或全无"两类场景下表达清晰。

#
★★★

5. StructuredTaskScope 的异常传播与 cancel 语义

StructuredTaskScope 的异常传播与 cancel 语义是什么?

  • 异常的收集与传播
  • cancel 机制
  • 关闭与结果

StructuredTaskScope 的异常传播:子任务异常被捕获,父作用域在 await/join 后通过 Subtask 句柄或传播机制获取。受检异常需包装,未检查异常通过 join 传播(如 ShutdownOnFailure 中,任一失败抛出的异常会传播给调用方)。cancel 语义:作用域关闭(shutdown)时,未完成子任务被取消(interrupt),StructuredTaskScope 会中断仍在运行的子任务,防止泄漏。join 返回后,父线程可收集结果或异常。异常传播是确定性的——所有子任务要么完成要么被取消,不会逃逸。

结构化并发的核心是"异常与取消的确定性"。cancel 确保子任务不悬挂,异常传播确保失败可见,避免并发任务的异常泄漏与资源泄漏。

#
★★★

6. Scoped Values 与 JDK Flight Recorder 的诊断

Scoped Values 与 JDK Flight Recorder 的诊断如何工作?

  • Scoped Values 的 JFR 事件
  • 诊断价值
  • 生产监控

JDK 提供 Scoped Values 的 JFR 诊断事件(如 jdk.ScopedValueBound、jdk.ScopedValueUnbound 等),记录 Scoped Value 的绑定与解绑,帮助诊断作用域传递问题(如值未正确绑定、泄漏、跨线程传递错误)。JFR 事件可配合方法级或线程级分析,定位 Scoped Value 的绑定范围与生命周期。生产监控中,可通过 JFR 事件观察 Scoped Values 的绑定情况,验证上下文传递是否正确。

Scoped Values 的 JFR 事件让作用域传递可观测。结合 JFR 的其他事件,可诊断上下文泄漏、绑定范围错误等结构化并发问题。

#
★★

7. Scoped Values 与 Micrometer MDC 的边界

Scoped Values 与 Micrometer MDC 的边界是什么?

  • Micrometer MDC 的用途
  • Scoped Values 与 MDC 的关系
  • 日志上下文传递

Micrometer MDC(Mapped Diagnostic Context)通常基于 ThreadLocal 存储日志上下文(如 traceId、requestId),用于日志关联。Scoped Values 与 MDC 的边界:MDC 是日志框架的上下文机制,Scoped Values 是更通用的不可变上下文传递机制。在虚拟线程下,基于 ThreadLocal 的 MDC 可能因虚拟线程复用而上下文泄漏,可考虑用 Scoped Values 承载 MDC 底层存储,但 MDC 的可变更新(如 put/remove)与 Scoped Values 不可变冲突。实践中,将 MDC 的值(traceId 等)作为 Scoped Value 绑定,在日志输出时读取 Scoped Value 填充 MDC,兼顾不可变与日志关联。

MDC 需要可变更新(增删 key),Scoped Values 不可变,两者语义有张力。边界是:用 Scoped Values 传递不可变的上下文(如 traceId),MDC 读取时从 Scoped Value 取用,避免 ThreadLocal 泄漏。

#
★★

8. Scoped Values 与 Web 请求上下文(Spring)

Scoped Values 与 Web 请求上下文(Spring)如何配合?

  • 请求上下文的传递
  • Scoped Values 在 Spring 的应用
  • 替代 ThreadLocal 上下文

在 Spring 中,请求上下文(如用户、请求 ID、事务)常通过 ThreadLocal 传递(如 RequestContextHolder、MDC)。Scoped Values 可替代 ThreadLocal 传递请求上下文:在请求入口处用 ScopedValue.where(key, value).run(...) 绑定请求上下文,在虚拟线程与结构化并发下自动传递给子任务,避免 ThreadLocal 在虚拟线程下的泄漏。Spring 6.1+ 与 Spring Boot 支持 Scoped Values 的集成(如自动将请求属性绑定为 Scoped Value)。配合 StructuredTaskScope 并行处理时,请求上下文通过 Scoped Value 明确传递到子任务。

Web 请求上下文是 Scoped Values 的典型场景。不可变、明确作用域、结构化继承让请求上下文在虚拟线程并发下安全传递,替代易泄漏的 ThreadLocal。

#
★★

9. Scoped Values 在 Spring Modulith 事件的传递

Scoped Values 在 Spring Modulith 事件的传递中如何工作?

  • Spring Modulith 事件模型
  • Scoped Values 的事件上下文
  • 跨模块传递

Spring Modulith 支持事件发布与订阅,事件处理后可能在不同线程(甚至虚拟线程)执行。Scoped Values 可在事件发布时绑定上下文(如事件 ID、用户、追踪信息),在事件订阅处理方法中通过 Scoped Value 读取,实现跨模块、跨线程的上下文传递。Modulith 的事件异步处理(如 @Async)配合 Scoped Values 可让上下文明确传递,避免 ThreadLocal 在线程池/虚拟线程下的泄漏。结构化并发下,事件子任务通过 Scoped Value 继承发布者的上下文。

事件驱动架构中上下文传递是难点。Scoped Values 提供不可变、结构化的上下文传递,配合 Modulith 事件在跨模块、跨线程处理时保持上下文一致。

#
★★

10. Scoped Values 在虚拟线程下的最佳实践

Scoped Values 在虚拟线程下的最佳实践是什么?

  • 绑定与读取
  • 结构化并发配合
  • 避免泄漏

Scoped Values 在虚拟线程下的最佳实践:1)在任务入口(如请求处理、线程起点)用 ScopedValue.where(key, value).run(...) 绑定上下文,避免在循环中反复绑定;2)配合结构化并发(StructuredTaskScope.fork),子任务自动继承父作用域的 Scoped Value(通过 scope 的继承机制);3)避免在虚拟线程中通过 ThreadLocal 传递上下文,改用 Scoped Value;4)用不可变值,谨慎处理可变状态;5)作用域内使用 try-with-resources 或 run 方法确保解绑,避免泄漏。这些实践让虚拟线程下的上下文传递安全、高效。

虚拟线程数量巨大,ThreadLocal 泄漏风险高。Scoped Values 的"入口绑定 + 结构化继承 + 作用域解绑"是虚拟线程下上下文传递的最佳实践。

#
★★

11. Scoped Values 的 ScopedValue.where(k, v).run(...) API

Scoped Values 的 ScopedValue.where(k, v).run(...) API 是什么?

  • where 绑定
  • run 执行
  • 作用域

ScopedValue.where(key, value).run(...) 是 Scoped Values 的核心 API:where 将 key 绑定到 value 并返回一个 ScopedValue.Carrier,run 在接收者的作用域内执行代码,在该作用域内(包括其中的线程与结构化子任务)可读取该 key。作用域结束后,绑定自动解除,key 恢复为未绑定或原值。run 可嵌套(多个 where 链式),也可用 Callable 返回结果。该 API 定义了值的"生命周期"="run 代码块",保证不可变与作用域内可见。

where/run 是 Scoped Values 的绑定与作用域模型。值只在 run 的执行范围内可见,作用域结束即失效,天然防止泄漏与跨域污染。

#
★★

12. Scoped Values 的 get() 抛出 NoSuchElementException 的边界

Scoped Values 的 get() 抛出 NoSuchElementException 的边界是什么?

  • get() 的异常
  • 未绑定场景
  • 处理方式

ScopedValue.get() 在 key 未绑定(未通过 where 绑定)时抛出 NoSuchElementException。边界包括:在 run 作用域外调用 get()、key 未被任何外层 where 绑定、或作用域已结束。处理方式:先用 isBound() 判断是否绑定,或用 seeIfPresent() 返回 Optional 避免异常,或提供默认值。get() 的异常语义提示代码必须确保在正确的作用域内访问 Scoped Value,否则视为编程错误。

get() 的 NoSuchElementException 是"未绑定即错误"的显式信号。用 isBound 或 seeIfPresent 可安全处理可选的上下文。

#
★★

13. Scoped Values 的 try/catch 与逃逸

Scoped Values 的 try/catch 与逃逸是什么?

  • 逃逸的禁止
  • 结构化并发的约束
  • 异常处理

Scoped Values 的逃逸约束:值绑定在 run 作用域内,不能逃逸到作用域外的线程(试图将 Scoped Value 传给非结构化线程会报错或无法访问)。这正是结构化并发的体现——值只在结构化子任务内继承。try/catch 方面:run 的执行代码可包含 try/catch 处理异常,异常不影响已绑定的值;但若在 run 外试图读取被绑定的值(逃逸),会得到未绑定或抛异常。使用时应确保在作用域内读取,异常处理与逃逸控制是 Scoped Values 安全性的关键。

逃逸控制是 Scoped Values 与 ThreadLocal 的本质区别:值不能逃逸到无关线程,从而避免上下文污染。try/catch 保证异常处理不破坏作用域绑定。

#
★★

14. Scoped Values 的继承(StructuredTaskScope.fork)

Scoped Values 的继承(StructuredTaskScope.fork)是如何工作的?

  • fork 的继承
  • 作用域快照
  • 子任务上下文

在 StructuredTaskScope 中 fork 子任务时,Scoped Values 会从父作用域继承给子任务:子任务在 fork 时捕获父级 Scoped Value 的绑定快照,子任务可读取这些值。这通过 ScopedValue 的 snapshot/restore 机制实现——fork 时保存当前绑定,子任务运行时恢复。继承是明确且结构化的:只有 fork 的子任务能继承,不会泄漏到无关线程。子任务也可在本地覆盖(where)某些值。继承让并行子任务共享父上下文(如请求 ID、用户)。

StructuredTaskScope.fork 的 Scoped Value 继承是"结构化上下文传递"的核心。fork 快照保证子任务看到一致的上下文,且不逃逸。

#
★★

15. StructuredTaskScope.join(Duration) 超时,超时后子任务继续运行与取消的取舍,如何避免资源泄漏

StructuredTaskScope.join(Duration) 超时后,子任务继续运行与取消的取舍是什么?如何避免资源泄漏?

  • join 超时
  • 超时后的子任务处理
  • 资源泄漏避免

StructuredTaskScope.join(Duration) 在指定超时内等待子任务完成,超时后返回,但子任务可能仍在运行。取舍:超时返回后,未完成子任务可能继续运行(占用资源)或被取消。为避免资源泄漏,应在超时后调用 scope.close() 或 shutdown() 取消未完成子任务,或显式 fork 的子任务在 join 超时后中断。structured 约定的最佳实践:在 try-with-resources 中创建 scope,超时后 close() 会取消所有未完成子任务并等待其结束,从而回收资源。若不取消,子任务可能悬挂导致资源泄漏。

join 超时后的取舍是"快速返回"与"资源回收"。结构化并发要求关闭作用域时取消未完成子任务,避免泄漏。用 try-with-resources + close() 可保证资源回收。

#
★★

16. Subtask 句柄的结果收集,遍历 fork 返回的引用、分类成功与失败并安全调用 get()

Subtask 句柄的结果收集如何工作?如何遍历、分类成功与失败并安全调用 get()?

  • Subtask 句柄
  • 结果分类
  • 安全 get()

StructuredTaskScope.fork 返回 Subtask 句柄,用于收集子任务结果。await 后,遍历所有 Subtask 句柄,通过 state() 分类(SUCCESS / FAILED / UNAVAILABLE),对成功者调用 get() 获取结果,对失败者处理异常。安全调用 get():仅对 state()==SUCCESS 的句柄调用 get()(否则抛异常),或在 get() 捕获异常。Subtask 句柄允许在作用域关闭后访问结果快照。遍历分类是实现"部分成功"聚合(如多数成功、收集所有成功结果)的标准方式。

Subtask 句柄提供了结果收集的细粒度控制。分类状态 + 安全 get 是聚合并发结果的正确模式,避免在失败句柄上调用 get() 抛异常。

#

17. Scoped Values(JEP 506 Final)的不可变特性

Scoped Values(JEP 506 Final)的不可变特性是什么?

  • 不可变绑定
  • 值语义
  • 与 ThreadLocal 的对比

JEP 506 将 Scoped Values 定稿为最终版,其不可变特性指:绑定到 Scoped Value 的值一旦绑定,在作用域内不可修改(绑定的引用不可变,指向不可变对象更佳)。Scoped Value 本身是 final 的 key,绑定值不可变,不能像 ThreadLocal 那样 set 新值。不可变性带来线程安全(无需同步)、可预测性(作用域内值稳定)与安全性(避免意外修改)。若需可变状态,应使用不可变容器的引用或重新绑定。定稿后 Scoped Values 稳定可用,是虚拟线程与结构化并发下推荐上下文机制。

不可变是 Scoped Values 的核心设计。它消除了 ThreadLocal 的可变与并发修改问题,让上下文传递可预测、线程安全。

#

18. Structured Concurrency 与虚拟线程的协作

Structured Concurrency 与虚拟线程如何协作?

  • 结构化并发的执行
  • 虚拟线程作为执行单元
  • 协作优势

Structured Concurrency(StructuredTaskScope)与虚拟线程协作:StructuredTaskScope 默认在虚拟线程上执行 fork 的子任务(JDK 21+ 结构化并发默认使用虚拟线程),使每个子任务都是独立的虚拟线程。虚拟线程的轻量让并行子任务数量可很大,结构化并发提供生命周期与取消管理。协作优势:一是子任务的海量并发(虚拟线程);二是确定性的生命周期与异常传播(结构化并发);三是阻塞 I/O 不阻塞载体线程。两者结合实现了"大规模并行 + 结构化安全"的并发模型。

虚拟线程提供"执行能力",结构化并发提供"结构约束"。两者天然契合,是 JDK 并发生态的推荐组合。

#

19. ScopedValue 的快照与恢复,snapshot/restore 如何被框架用来保存并恢复调用上下文

ScopedValue 的快照与恢复(snapshot/restore)如何被框架用来保存并恢复调用上下文?

  • snapshot 与 restore
  • 框架的上下文保存
  • 结构化并发的应用

ScopedValue 提供 snapshot() 保存当前所有 Scoped Value 的绑定快照,restore() 恢复到一个快照。框架(如并发执行器、结构化并发、异步框架)用它保存并恢复调用上下文:在任务进入时 snapshot 当前绑定,任务执行时在快照基础上绑定/运行,任务结束后 restore 到原状态,或在新线程中通过 restore 恢复捕获的上下文。例如 StructuredTaskScope.fork 用 snapshot 捕获父上下文传给子任务。快照机制让框架能精确保存/恢复不可变上下文,避免手动逐 key 传递。

snapshot/restore 是框架实现"上下文捕获与恢复"的底层机制。它让上下文传递程序化、可批量,是结构化并发与异步框架的关键基础设施。

#

20. StructuredTaskScope 的 fork 执行器,默认在虚拟线程运行,如何通过工厂切换执行方式

StructuredTaskScope 的 fork 执行器如何工作?默认在虚拟线程运行,如何通过工厂切换执行方式?

  • fork 的默认执行器
  • 执行器工厂
  • 切换执行方式

StructuredTaskScope 的 fork 默认在虚拟线程上运行子任务(JDK 21+ 默认使用虚拟线程执行器)。如需切换执行方式,可通过创建 scope 时传入自定义执行器(如 ExecutorService 的工厂)来控制子任务的执行,例如使用固定线程池或自定义 Executor。StructuredTaskScope 构造器可接受 Executor(或工厂),fork 的任务在该执行器上运行。切换场景:需要限制并发、复用线程池、或兼容非虚拟线程环境时指定自定义执行器。

fork 默认虚拟线程是"开箱即用"的默认,执行器工厂提供灵活性。传递自定义 Executor 可控制子任务的执行方式,满足特定资源或兼容需求。