Java 11 HTTP 客户端异步执行

Posted

技术标签:

【中文标题】Java 11 HTTP 客户端异步执行【英文标题】:Java 11 HTTP client asynchronous execution 【发布时间】:2019-01-25 05:16:17 【问题描述】:

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

返回的CompletableFuture 实例的异步任务和相关操作在可行的情况下在客户端Executor 提供的线程上执行。

据我了解,这意味着如果我在创建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

这是实现中的错误吗?或者我错过了什么?我注意到它说“实例是在客户端执行器提供的线程上执行的,在实用的地方”,那么这是不适用的情况吗?

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

【问题讨论】:

对不起,如果这很愚蠢,但请帮助我理解,您是如何解释 它来自公共池而不是自定义执行器,因为它打印线程是:ForkJoinPool.commonPool-worker-5 ?...我还在thenAccept 消费者中尝试了System.out.println(httpClient.executor().get().equals(executor));,它打印出true @nullpointer 我假设他在thenAccept Consumer 中打印出Thread.currentThread().getName() 并且名称表明Thread 来自常见的ForkJoinPool,而不是自定义的Executor .换句话说,OP 并不是说​​HttpClientExecutor 发生了更改,OP 想知道为什么依赖的CompletableFuture 阶段是使用不同的线程池执行的。 @nullpointer 正是 Slaw 所说的。我也知道线程来自公共池,因为我可以给自定义执行器创建的线程特殊名称以清楚地识别它们。至于httpClient.executor(),这个方法只是返回我创建时指定的执行器,不是thenAccept使用的。 @Slaw @manouti 谢谢。我得到了你们俩所指的内容,确实尝试向执行程序提供一个自定义命名线程,并且可以看到它没有在thenAccept 中使用。将进一步寻找有关实用部分的详细信息以及错误数据库。 原来在这个 API 的过程中文档已经被更新了,所以它描述了这个行为。最新的文档链接是 download.java.net/java/early_access/jdk11/docs/api/… 【参考方案1】:

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

一般来说,异步任务在调用操作的线程中执行,例如sending 一个 HTTP 请求,或者由客户端的 executor 提供的线程。 相关任务,由返回的 CompletionStages 或 CompletableFutures 触发,未明确指定执行者,在与 CompletableFuture 相同的 default executor 中执行,或者在操作完成时在调用线程中执行在依赖任务注册之前。

CompletableFuture的默认执行者是公共池。

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

2) 依赖任务在公共池中运行 依赖任务的默认执行已更新为在与 CompletableFuture 的 defaultExecutor 相同的执行器中运行。这对于已经使用 CF 的开发人员来说更为熟悉,并减少了 HTTP 客户端因线​​程不足而无法执行其任务的可能性。这只是默认行为,如果需要,HTTP 客户端和 CompletableFuture 都允许更细粒度的控制。

【讨论】:

这只是默认行为,如果需要,HTTP 客户端和 CompletableFuture 都允许更细粒度的控制。” 对于CompletableFuture,我假设他的意思是使用*Async(…, Executor) 方法变体。但是对于 HTTP 客户端,我看不出它如何帮助控制这种行为。我发现在任何地方使用异步方法变体都不是很方便。 这也是我读到这一点时的想法;只有构建器允许在 HTTP 客户端中传递执行器,这甚至不会影响 CF 中的依赖任务。此外,即使是最近的文档似乎也与it still says“将执行程序设置为用于异步和相关任务”不一致。也许他们错过了更新那部分。 我赞成@manouti 的回答,这是正确的。此外,还提交了以下错误以修复错误的规范,即依赖任务在执行程序中运行,bugs.openjdk.java.net/browse/JDK-8209943 @chegar999 酷!感谢您提供此 API!仅供参考,我在 here 的帖子中总结了一些使用 API 的示例,在此期间我遇到了这种行为。【参考方案2】:

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

详细说明:

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

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

/**
 * 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 为真,则此类使用executor,否则内联执行任务。这归结为:

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

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

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

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

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

【讨论】:

感谢您的回答。它确实似乎是一个实现细节。但是,关于您的最后一条说明,当我没有在客户端设置中指定执行程序时,相关操作仍然使用公共池,而不是幕后的默认线程缓存执行程序。

以上是关于Java 11 HTTP 客户端异步执行的主要内容,如果未能解决你的问题,请参考以下文章

jdk11新特性——标准Java异步HTTP客户端

jdk11新特性——标准Java异步HTTP客户端

java 一个http客户端,不仅可以处理同步请求,还可以处理异步请求。

发送多个同时请求时,单线程异步系统中野兽增强异步 http 客户端的行为

grpc 完成异步 java 服务请求/响应映射

Voovan 是一个高性能异步网络框架和 HTTP(Java)