scopedvalue 不传播异常且子任务不继承父scope值,需用copyforchild()显式传递;structuredtaskscope的异常藏在executionexception.getcause()中,须拆解处理。

ScopedValue 在异常路径下不会自动传播异常
Java 21 的 ScopedValue 本身不参与异常传递链,它只是线程局部值的轻量载体。哪怕你在 ScopedValue.where() 的作用域里抛出异常,这个异常也不会“带上” ScopedValue 的上下文信息——JVM 不会把 scoped 值塞进 Throwable.getSuppressed() 或类似机制里。
常见错误现象:ScopedValue.get() 在异常处理块中调用时返回 null(如果未设默认值),或抛出 IllegalStateException(如果 scope 已退出),但你误以为是原始业务异常导致的。
- 使用场景:适合传递只读上下文(如 traceId、userId),不适合传递需参与错误恢复的“状态句柄”
- 参数差异:
ScopedValue.where()第二个参数是 Supplier,不是 Runnable;别写成where(key, () -> { doSomething(); throw new RuntimeException(); })—— 异常就直接往外扔,和 scoped 值无关 - 性能影响:无额外异常开销,但若在
catch块中反复调用ScopedValue.get(),要注意 scope 生命周期是否已结束
结构化并发(StructuredTaskScope)中异常被吞掉的典型原因
Java 21 的 StructuredTaskScope 默认行为是:只要有一个子任务失败,join() 就立即抛出第一个异常,其余任务会被取消。但如果你用了 StructuredTaskScope.ShutdownOnFailure 却没 catch 住 ExecutionException,或者用了 ShutdownOnSuccess 却忽略 get() 的返回值检查,异常就“消失”了。
常见错误现象:日志里看不到任何报错,但结果为空或逻辑跳过;调试时发现某个 fork 的任务明明抛了 IOException,主线程却继续执行。
立即学习“Java免费学习笔记(深入)”;
基于Intranet/Internet 的Web下的办公自动化系统,采用了当今最先进的PHP技术,是综合大量用户的需求,经过充分的用户论证的基础上开发出来的,独特的即时信息、短信、电子邮件系统、完善的工作流、数据库安全备份等功能使得信息在企业内部传递效率极大提高,信息传递过程中耗费降到最低。办公人员得以从繁杂的日常办公事务处理中解放出来,参与更多的富于思考性和创造性的工作。系统力求突出体系结构简明
- 使用场景:适用于必须等全部完成(
ShutdownOnSuccess)或只要一个失败就中止(ShutdownOnFailure)的协作型任务 - 参数差异:注意
StructuredTaskScope是泛型类,StructuredTaskScope<string></string>的join()抛的是ExecutionException,不是原始IOException;原始异常藏在e.getCause()里 - 兼容性影响:Java 21+ 才有,且必须运行在支持虚拟线程的模式下(
--enable-preview --virtual-threads),否则编译通过但运行时报NoClassDefFoundError
ScopedValue + StructuredTaskScope 组合时,子任务拿不到父 scope 值
虚拟线程默认不继承父线程的 ScopedValue,哪怕你用 StructuredTaskScope.fork() 启动的也是新虚拟线程。这是设计使然,不是 bug —— ScopedValue 的传播需要显式启用。
常见错误现象:主线程设置了 ScopedValue.where(user, "alice"),fork 出的任务里调用 user.get() 报 IllegalStateException: No value present。
- 解决方法:必须用
ScopedValue.copyForChild()显式传播,例如user.copyForChild().where("alice", () -> scope.fork(...)) - 性能影响:每次
copyForChild()都是浅拷贝,开销极小;但嵌套太深(比如递归 fork)可能累积少量对象引用 - 容易踩的坑:别在
fork()外层用where()包裹整个 scope 块——那只是给外层线程设值,对子虚拟线程无效
捕获结构化并发异常后想补救?别直接 try-catch ExecutionException
真正要处理的是 ExecutionException.getCause(),而且得区分类型:如果是 InterruptedException 或 TimeoutException,通常该重试或降级;如果是业务异常(如 ValidationException),才该记录并返回错误响应。
常见错误现象:统一 catch ExecutionException 然后 log + rethrow,结果所有异常都被当“系统故障”告警,掩盖了可恢复的业务问题。
- 实操建议:用
instanceof拆解e.getCause(),优先处理InterruptedException(清理资源后 return)、再处理具体业务异常 - 示例:
try { scope.join(); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof ValidationException v) { return buildErrorResponse(v); } else if (cause instanceof InterruptedException) { Thread.currentThread().interrupt(); return null; } throw new RuntimeException(cause); } - 注意点:不要在
join()后再调用scope.results()—— 如果已有异常,results 可能为空或不完整







