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 并不是说HttpClient
的Executor
发生了更改,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;
其中selmgr
是SelectorManager
。 编辑:这个类也包含在HttpClientImpl.java
:
// Main loop for this client's selector
private final static class SelectorManager extends Thread
结果:我猜在实际应用中意味着它依赖于实现,并且不能保证提供的executor
将被使用。
注意:这与默认执行程序不同,其中构建器不提供executor
。在这种情况下,代码显然会创建一个新的缓存线程池。换句话说,如果构建器提供executor
,则对SelectorManager
进行身份检查。
【讨论】:
感谢您的回答。它确实似乎是一个实现细节。但是,关于您的最后一条说明,当我没有在客户端设置中指定执行程序时,相关操作仍然使用公共池,而不是幕后的默认线程缓存执行程序。以上是关于Java 11 HTTP 客户端异步执行的主要内容,如果未能解决你的问题,请参考以下文章
java 一个http客户端,不仅可以处理同步请求,还可以处理异步请求。