java - Java中链式方法回调会导致堆栈溢出吗?

标签 java callback stack-overflow

我的应用程序正在处理大量 HTTP 请求/响应事务,其中一个服务的响应会导致下一步和后续请求,依此类推一系列步骤。

为了使代码优雅,我使用了下载和回调结构,可以简单地表示如下:

private void runFirstStep() {
    String firstRequest = buildRequest();
    sendHttpRequest(firstRequest, this::handleFirstStepResponse);
}

private void handleFirstStepResponse(InputStream responseBody) {
    doStuffWithFirstStepResponse(responseBody);
    String secondRequest = buildSecondRequest();
    sendHttpRequest(secondRequest, this::handleSecondStepResponse);
}

private void handleSecondStepResponse(InputStream responseBody) {
    doStuffWithSecondStepResponse(responseBody);
    String thirdRequest = buildThirdRequest();
    sendHttpRequest(thirdRequest, this::handleThirdStepResponse);
}

private void handleThirdStepResponse(InputStream responseBody) {
    doStuffWithThirdStepResponse(responseBody);
    // The flow has finished, so no further HTTP transactions.
}

尽管在我的例子中,序列长度目前达到大约 26 个步骤,全部以这种方式链接。

这工作正常,但我碰巧注意到控制台中的日志记录行,这清楚地表明每个方法只是等待链中的所有其他方法完成(当我想到这一点时,这是显而易见的) )。但这也让我认为我使用的这种模式可能有导致堆栈溢出的风险。

所以问题是:

  1. 像这样的序列(可能有几十个链接步骤)是否会面临堆栈溢出的风险,或者是否需要比这更多的滥用才能耗尽典型的堆栈?请注意,上面的简化代码结构隐藏了这样一个事实:这些方法实际上执行了很多操作(构建 XML、提取 XML、将请求/响应数据记录到日志文件),因此我们不是在讨论轻量级任务。

  2. 我是否应该使用一种不同的模式,该模式不会留下一系列方法,所有方法都耐心地等待整个流程完成?我的sendHttpRequest方法已经使用 JDK 11 HTTP 框架生成 CompletableFuture<HttpResponse<InputStream>> ,但是我的sendHttpRequest然后,方法只需等待该过程完成并使用结果调用指定的回调方法。是否应该创建一个新线程来处理 CompletableFuture ,以便调用方法可以优雅地关闭?以及如何在不导致 JVM 关闭的情况下执行此操作(因为缓慢的 HTTP 响应将使 JVM 无法同时执行任何方法)?

作为 Stack Overflow(该网站,并非异常(exception)),我当然正在寻找涉及 Java 裸机机制的答案,而不是猜测或轶事。

更新:只是为了澄清一下,我的 sendHttpRequest方法目前具有这种形状:

private void sendHttpRequest(String request,
        Consumer<InputStream> callback) {
    HttpRequest httpRequest = buildHttpRequestFromXml(request);
    CompletableFuture<HttpResponse<InputStream>> completableExchange
            = httpClient.
            sendAsync(httpRequest, BodyHandlers.ofInputStream());
    HttpResponse<InputStream> httpResponse = completableExchange.join();
    InputStream responseBody = getBodyFromResponse(httpResponse);
    callback.accept(responseBody);
}

重要的一点是 Java 的 HttpClient.sendAsync方法返回 CompletablFuture ,然后 join()对该对象进行调用以等待接收 HTTP 响应并以 HttpResponse 形式返回对象,然后用于将响应正文提供给指定的回调方法。但我的问题并不是专门关于 HTTP 请求/响应序列,而是处理任何适合等待结果和回调结构的流时的风险和最佳实践。

最佳答案

首先,如果一个方法将回调作为参数,它不应该阻塞调用线程。如果你正在阻塞,则不需要回调。(你可以从 sendHttpRequest 返回 InputStream 并用它调用下一个方法。)

您应该使用 CompletableFuture 实现完全异步。但这里您必须考虑一件事。当并行流操作和 CompletableFuture 不是专门在 Executor(线程池)上执行时,它们会使用公共(public)池。由于http下载是阻塞操作,因此您不应在公共(public)池中执行它(以免阻塞公共(public)池线程执行IO操作)。您应该创建 IO 池并将其传递给 CompletableFuture 方法,该方法在下载时以 Executor 作为参数。

至于如果继续当前的设计会发生什么;

当调用一个方法时,将创建一个堆栈帧并将其推送到调用线程的堆栈。该框架将保存该方法调用位置的返回地址、该方法采用的参数以及该方法的局部变量。如果这个参数和变量是基本类型,它们将存储在堆栈中,如果它们是对象,它们的地址将存储在堆栈中。当该方法执行完毕后,其框架将被销毁。

26 个方法调用链不应成为堆栈溢出问题。您还可以使用 -Xss 开关控制堆栈大小。每个平台的默认堆栈大小会有所不同(32 位和 64 位也会影响默认大小。)如果您正在为应用程序提供可执行命令,如果您担心这一点,您可能需要定义此值。

关于java - Java中链式方法回调会导致堆栈溢出吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54845181/

相关文章:

java - Java中@Autowired注解的好处

c# - 具有后备属性的 setter 中的 StackOverflowException

java - 冒泡排序 StackOverflowError

swift - 我如何从另一个应用程序获得回调

python - 使用 lambda 在回调中设置属性

recursion - OCaml 评估期间的堆栈溢出

java - 西蒙说按下按钮后按钮颜色不会恢复正常

java - Spring CrudRepository 未保存超过 32,000 个字符

Java 字符串初始化默认转换为 char 数组

javascript - 如何从 chrome 扩展打开选项卡等待用户输入并返回扩展?