You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CompletableFuture异步调用未等待所有URL请求完成问题求助

根因说明

你代码偶发缺失请求结果的核心原因是使用了非线程安全的ArrayList作为结果收集容器,多个异步回调同时执行llll::add时会出现元素覆盖、丢失的问题,并非CompletableFuture.allOf没有等待请求完成。

实现方案

我们针对你的三个需求做了对应改造:

  • 保留allOf.join()逻辑保证所有请求完成后才走后续流程
  • 不使用共享容器存储中间结果,避免并发写冲突,最终统一收集所有结果到List
  • 支持通过键值对为请求绑定自定义标签,结果中保留状态码、响应体、异常信息等校验所需字段

完整可运行代码

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

// 自定义请求结果实体,存储所有校验需要的字段
class RequestResult {
    // 请求标签
    private final String tag;
    // 响应状态码,请求异常时为-1
    private final int statusCode;
    // 响应体,请求异常时为null
    private final String body;
    // 异常信息,请求成功时为null
    private final String errorMsg;

    public RequestResult(String tag, int statusCode, String body, String errorMsg) {
        this.tag = tag;
        this.statusCode = statusCode;
        this.body = body;
        this.errorMsg = errorMsg;
    }

    // Getter方法
    public String getTag() { return tag; }
    public int getStatusCode() { return statusCode; }
    public String getBody() { return body; }
    public String getErrorMsg() { return errorMsg; }
}

class HelloWorld {
    // 入参改为标签-URL的键值对,支持打分类标签
    private static List<RequestResult> concurrentCalls(Map<String, String> tagToUrl) {
        var client = HttpClient.newHttpClient();

        // 每个请求对应一个返回RequestResult的CompletableFuture
        List<CompletableFuture<RequestResult>> futureList = tagToUrl.entrySet().stream()
                .map(entry -> {
                    String tag = entry.getKey();
                    String url = entry.getValue();
                    HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                            .GET()
                            .timeout(Duration.ofSeconds(20))
                            .build();
                    // 异步发送请求,封装结果为RequestResult
                    return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                            .thenApply(response -> new RequestResult(
                                    tag,
                                    response.statusCode(),
                                    response.body(),
                                    null
                            ))
                            .exceptionally(e -> new RequestResult(
                                    tag,
                                    -1,
                                    null,
                                    "请求异常: " + e.getMessage()
                            ));
                })
                .collect(Collectors.toList());

        // 等待所有请求完成(无论成功失败)
        CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).join();

        // 所有请求完成后统一收集结果到List
        List<RequestResult> resultList = new ArrayList<>();
        for (CompletableFuture<RequestResult> future : futureList) {
            try {
                resultList.add(future.get());
            } catch (Exception e) {
                // 异常已经在exceptionally处理过,理论上不会走到这里
                resultList.add(new RequestResult("unknown", -1, null, "未知异常: " + e.getMessage()));
            }
        }
        return resultList;
    }

    public static void main(String[] args) {
        // 传入标签和URL的映射,按你的需求自定义标签即可
        Map<String, String> tagToUrl = Map.of(
                "正常get请求", "https://postman-echo.com/get?foo1=bar1&foo2=bar2",
                "自定义响应头请求", "https://postman-echo.com/response-headers?foo1=bar1&foo2=bar2",
                "参数非法请求测试", "https://postman-echo.com/status/400"
        );
        List<RequestResult> results = concurrentCalls(tagToUrl);

        // 遍历结果做断言校验示例
        for (RequestResult result : results) {
            System.out.println("标签:" + result.getTag());
            System.out.println("状态码:" + result.getStatusCode());
            if (result.getErrorMsg() != null) {
                System.out.println("异常信息:" + result.getErrorMsg());
            } else {
                System.out.println("响应体:" + result.getBody().substring(0, 100) + "...");
            }
            System.out.println("---------------------------");
        }
    }
}

方案说明

  • 等待逻辑:CompletableFuture.allOf会聚合所有异步任务,调用join()会阻塞当前线程直到所有任务执行完成,保证不会提前进入结果处理流程
  • 结果收集:所有任务完成后才统一遍历futureList提取结果,完全避免了多线程并发写容器的问题,不会出现结果丢失
  • 标签能力:入参使用Map结构绑定标签和请求地址,结果中会携带对应标签,可直接通过标签区分不同类型的请求
  • 校验支持:结果实体包含了标签、状态码、响应体、异常信息所有你需要的字段,后续直接调用对应Getter方法即可做断言校验

内容的提问来源于stack exchange,提问作者Ionutz Asaftei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 14:24:02