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

问题描述 投票:2回答:1

我的应用程序正在处理大量的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在此期间没有执行任何方法)?

正在堆栈溢出(网站,而不是例外)我当然在寻找涉及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请求/响应序列,而是处理任何类型的流程时的风险和最佳实践,这些流程有助于等待结果和回调结构。

java callback stack-overflow
1个回答
2
投票

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

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

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.