Java11 HTTP客户端异步执行

Java 11 HTTP client asynchronous execution

我正在尝试 JDK 11 中的新 HTTP 客户端 API,特别是它执行请求的异步方式。但是有些事情我不确定我是否理解(某种实现方面)。在 documentation 中,它表示:

Asynchronous tasks and dependent actions of returned CompletableFuture instances are executed on the threads supplied by the client's Executor, where practical.

据我了解,这意味着如果我在创建 HttpClient 对象时设置自定义执行程序:

ExecutorService executor = Executors.newFixedThreadPool(3);

HttpClient httpClient = HttpClient.newBuilder()
                      .executor(executor)  // custom executor
                      .build();

那么如果我异步发送请求并在返回的 CompletableFuture 上添加依赖操作,依赖操作应该在指定的执行器上执行。

httpClient.sendAsync(request, BodyHandlers.ofString())
          .thenAccept(response -> {
      System.out.println("Thread is: " + Thread.currentThread().getName());
      // do something when the response is received
});

但是,在上面的依赖操作中(thenAccept 中的消费者),我看到执行它的线程来自公共池而不是自定义执行程序,因为它打印 Thread is: ForkJoinPool.commonPool-worker-5

这是实现中的错误吗?或者我错过了什么?我注意到它说 "instances are executed on the threads supplied by the client's Executor, where practical",所以这是不适用的情况吗?

请注意,我也尝试了 thenAcceptAsync,结果相同。

简短版本:我认为您已经确定了一个实现细节,并且“实用的地方”意味着不能保证所提供的 executor 将被使用。

详细:

我已经从 here 下载了 JDK 11 源。 (撰写本文时 jdk11-f729ca27cf9a)。

src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java中,有如下class:

/**
 * A DelegatingExecutor is an executor that delegates tasks to
 * a wrapped executor when it detects that the current thread
 * is the SelectorManager thread. If the current thread is not
 * the selector manager thread the given task is executed inline.
 */
final static class DelegatingExecutor implements Executor {

如果isInSelectorThread 为真,则此class 使用executor,否则内联执行任务。这归结为:

boolean isSelectorThread() {
    return Thread.currentThread() == selmgr;
}

其中 selmgrSelectorManager编辑:这个class也包含在HttpClientImpl.java中:

// Main loop for this client's selector
private final static class SelectorManager extends Thread {

结果:我猜 实用的地方 意味着它依赖于实现,并且不能保证所提供的 executor 将被使用。

注意:这与默认执行程序不同,其中构建器不提供 executor。在那种情况下,代码显然会创建一个新的缓存线程池。换句话说,如果构建器提供 executor,则会对 SelectorManager 进行身份检查。

我刚刚找到了一个更新的 documentation(我最初链接到的那个看起来很旧),它解释了这个实现行为:

In general, asynchronous tasks execute in either the thread invoking the operation, e.g. sending an HTTP request, or by the threads supplied by the client's executor. Dependent tasks, those that are triggered by returned CompletionStages or CompletableFutures, that do not explicitly specify an executor, execute in the same default executor as that of CompletableFuture, or the invoking thread if the operation completes before the dependent task is registered.

并且CompletableFuture的默认执行器是公共池。

我还找到了介绍此行为的 bug ID,API 开发人员在其中对其进行了全面解释:

2) Dependent tasks run in the common pool The default execution of dependent tasks has been updated to run in the same executor as that of CompletableFuture's defaultExecutor. This is more familiar to developers that already use CF, and reduces the likelihood of the HTTP Client being starved of threads to execute its tasks. This is just default behaviour, both the HTTP Client and CompletableFuture allow more fine-grain control, if needed.