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

时间:2019-02-23 19:11:33

标签: 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在此期间不执行任何方法)?

成为堆栈溢出(该站点,也不例外),我当然在寻找与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响应被接收并作为{{ 1}}对象,该对象然后用于将响应主体提供给指定的回调方法。但是我的问题不是专门关于HTTP请求/响应序列,而是在处理任何可能导致等待结果和回调结构的流时的风险和最佳实践。

1 个答案:

答案 0 :(得分:2)

首先,如果方法将callback作为参数,则不应阻塞调用线程。

如果您正在阻止,则无需回调。(您可以从sendHttpRequest返回InputStream并使用它调用下一个方法。)

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

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

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

26个方法调用的链对于堆栈溢出应该不是问题。您也可以使用-Xss开关控制堆栈大小。每个平台的默认堆栈大小都将有所不同(32位和64位也会影响默认大小。)如果您为应用交付可执行命令,则可能需要定义该值。