当前位置:首页 > 文章列表 > 文章 > java教程 > Java StructuredTaskScope 怎样收集部分成功结果

Java StructuredTaskScope 怎样收集部分成功结果

来源:17golang原创 2026-11-06 20:02:43 0浏览 收藏

当一组并发子任务中有几项成功、几项失败时,StructuredTaskScope.open() 的默认策略并不会自动给你一份“成功项列表”:默认策略面向的是全部成功或任一失败。要收集部分成功结果,应为作用域提供自定义 StructuredTaskScope.Joiner,在 onComplete 中只接收 SUCCESS 状态,再让 join() 返回收集结果。

官方地址:https://openjdk.org/jeps/505

本文按当前 Java SE 27 API 的预览写法说明。StructuredTaskScope 和 Joiner 属于预览 API,编译与运行时需要启用预览开关;不同 JDK 版本的 Joiner 泛型和异常签名可能不同,升级时要以对应版本的官方 API 为准。

StructuredTaskScope 中成功与失败子任务进入 onComplete 收集器并形成成功结果列表的结构说明图
图1:成功与失败子任务进入自定义 Joiner 的静态结构说明图,不是运行截图或执行证据。

先判断你要的是哪一种结果

StructuredTaskScope 把多个子任务放进同一个作用域,调用 fork 启动任务,再由作用域拥有者调用一次 join 等待结果。真正决定“失败后返回什么”的不是 fork,而是 Joiner 的策略。

策略join 返回适合场景
allSuccessfulOrThrow()全部成功结果;任一失败就抛异常聚合接口必须完整
anySuccessfulOrThrow()任一成功结果;全部失败才抛异常多来源竞速或备选服务
自定义 Joiner按业务规则返回部分结果允许降级、保留成功项

因此,标题中的“部分成功”不是把 allSuccessfulOrThrow 的异常吞掉,而是把完成事件交给一个明确的收集策略。失败项是否记录、是否提前取消、超时后是否返回已有结果,都应在这个策略里写清楚。

先用最小自定义 Joiner 收集成功值

下面的示例以 Java 27 的三泛型 Joiner API 为例:第一个泛型是子任务结果类型,第二个是 join() 的返回类型,第三个是 Joiner 可能抛出的异常类型。onComplete 可能被多个线程并发调用,所以不能用没有同步保护的普通 ArrayList。

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.StructuredTaskScope;

final class PartialSuccessJoiner
        implements StructuredTaskScope.Joiner, RuntimeException> {
    // 完成回调可能并发执行,用线程安全队列保存成功值。
    private final ConcurrentLinkedQueue successes = new ConcurrentLinkedQueue();

    @Override
    public boolean onComplete(StructuredTaskScope.Subtask extends T> subtask) {
        // 只有 SUCCESS 才能调用 get,FAILED 和 UNAVAILABLE 没有可读结果。
        if (subtask.state() == StructuredTaskScope.Subtask.State.SUCCESS) {
            successes.add(subtask.get());
        }
        // false 表示不要因为一个完成事件就取消剩余子任务。
        return false;
    }

    @Override
    public List result() {
        // join 等待作用域内任务完成后,把当前成功值冻结成不可变列表。
        return List.copyOf(successes);
    }

    @Override
    public List timeout() {
        // 这里选择超时也返回已经收集的成功值;严格业务可改为抛出异常。
        return List.copyOf(successes);
    }
}

public class PartialSuccessDemo {
    static  List collectPartial(List> tasks)
            throws InterruptedException {
        try (var scope = StructuredTaskScope.open(new PartialSuccessJoiner())) {
            // fork 只负责启动,结果由 Joiner 的完成回调统一收集。
            tasks.forEach(scope::fork);
            return scope.join();
        } // try-with-resources 负责关闭作用域并等待子线程结束。
    }
}

这段实现的关键不是“忽略异常”,而是只在状态为 SUCCESS 时调用 get()。失败子任务仍然会完成并触发回调,只是没有被放进成功值队列;作用域不会因为单个失败而提前取消。

为什么必须使用线程安全收集器

Joiner 的 onComplete 回调由子任务完成事件触发,多个子任务可能在相近时刻完成。一个普通的 ArrayList 在并发追加时会有数据竞争,轻则丢项,重则破坏内部数组状态。

ConcurrentLinkedQueue 适合这个收集点:回调只追加,最终在 result() 中通过 List.copyOf 生成稳定快照。快照的顺序是完成顺序,不是 fork 的提交顺序;如果业务要求按输入顺序返回,应把任务编号和值一起收集,再在返回前排序。

还有一个重要边界:Subtask.get() 只应在子任务状态为 SUCCESS 时调用。FAILED 表示任务抛出了异常,UNAVAILABLE 表示结果不可用,例如作用域被取消后任务尚未完成。把所有状态都直接 get() 会把“部分成功”再次变成异常路径。

三种策略放在一起就能看出差异

allSuccessfulOrThrow、anySuccessfulOrThrow 与自定义 Joiner 对应不同返回结果的策略对比说明图
图2:三个 Joiner 策略与返回结果的关系说明图,不是官方界面截图。

allSuccessfulOrThrow 适合“少一个结果就不能继续”的聚合任务;它的返回值表达完整成功。anySuccessfulOrThrow 适合“拿到一个可用结果就够了”的竞速任务,成功后可能取消还在执行的任务。自定义 Joiner 则把取消条件和返回值拆开,可以让失败项不影响已经成功的结果。

不要为了得到部分结果而捕获 ExecutionException 后再尝试读取所有子任务。默认 Joiner 的失败语义已经决定了作用域的结束方式,事后捕获无法可靠地表达“哪些任务已经成功、哪些任务因为取消而不可用”。在事件回调里记录状态,才有清晰的边界。

用预期状态判断实现是否符合需求

假设有三个任务:A 返回“库存”、B 返回“价格”、C 抛出异常。自定义 Joiner 的结果应当是包含 A、B 的列表,而不是因为 C 失败就整体抛错。若 C 先失败,也不能因此读取 C 的值;它只会被忽略或写入单独的失败记录。

List> tasks = List.of(
        () -> "库存: 有货",
        () -> "价格: 99",
        () -> { throw new IllegalStateException("推荐服务暂不可用"); }
);

// 这里的调用只消费成功结果;InterruptedException 仍需交给上层处理。
List available = PartialSuccessDemo.collectPartial(tasks);
// 结果的元素顺序按完成先后变化,不应把它当成输入顺序。
System.out.println(available);

如果业务还要展示失败原因,可以在 Joiner 内增加一个线程安全的失败集合,例如保存任务标识与异常对象;不要把异常文本拼进成功值列表。这样上层可以分别决定“展示已有数据”“记录降级原因”以及“是否触发补偿”。

超时、取消和预览 API 要单独处理

示例中的 timeout() 选择返回截至超时已经收集的成功值,但这是一项业务决定,不是所有场景都安全。对于结算、扣款、写入这类必须完整的操作,超时应抛出异常并交给上层回滚或重试;对于搜索聚合、推荐、可选指标,返回部分结果才可能合理。

同时要注意预览 API 的版本边界:

  • 需要在编译和运行时启用 --enable-preview,并配合目标 JDK 的 --release。
  • JDK 25、26、27 的 StructuredTaskScope 与 Joiner 仍在演进,Joiner 的泛型、异常类型和方法名可能不同。
  • 作用域只能由拥有者线程执行 fork、join 和关闭;不要把 scope 交给普通工作线程操作。
  • 关闭作用域仍会等待其子线程结束;部分成功不等于可以遗留未收尾的线程。
# 使用与目标 JDK 对应的预览开关编译和运行示例。
javac --enable-preview --release 27 PartialSuccessDemo.java
java --enable-preview PartialSuccessDemo

最后按这张表选择实现

需求推荐策略关键风险
所有依赖都必须成功allSuccessfulOrThrow任一失败都会让整体失败
只要一个可用结果anySuccessfulOrThrow其它任务可能被取消
成功项可用,失败项降级自定义 Joiner必须设计状态收集、超时和错误记录

所以,Java StructuredTaskScope 收集部分成功结果的落点是自定义 Joiner:在 onComplete 中检查状态、用线程安全容器收集成功值,再由 result() 或 timeout() 决定返回边界。只要把失败、取消、顺序和预览版本写进契约,部分成功就不会变成含糊的异常吞噬。

相关问题

失败子任务能不能直接调用 get?

不能。先判断状态为 SUCCESS,只有成功状态才有可读结果。

成功结果的顺序固定吗?

自定义队列通常反映完成顺序,不保证输入顺序。需要稳定顺序时,请同时保存任务编号并在返回前排序。

为什么不用 anySuccessfulOrThrow?

它表达的是“任一成功即可”,成功后会取消其它任务;如果需要保留多个成功值,就应使用自定义 Joiner。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Java ModuleDescriptor.Provides 怎样读取服务提供者的类名Java ModuleDescriptor.Provides 怎样读取服务提供者的类名
上一篇
Java ModuleDescriptor.Provides 怎样读取服务提供者的类名
Java virtual threads 怎样让任务支持可取消超时
下一篇
Java virtual threads 怎样让任务支持可取消超时
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    912次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    919次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1003次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    995次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    815次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码