在关于JDK11 HttpClient的教程中,使用了一个在1秒后返回HTTP 500的https://httpstat.us/500?sleep=1000端点,我准备了以下代码:
HttpClient client = HttpClient.newHttpClient();
var futures = Stream.of(
"https://httpstat.us/500?sleep=1000",
"https://httpstat.us/500?sleep=1000",
"https://httpstat.us/500?sleep=1000"
).map(link -> client
.sendAsync(
newBuilder(URI.create(link)).GET().build(),
HttpResponse.BodyHandlers.discarding()
).thenApply(HttpResponse::statusCode)
).collect(Collectors.toList());
futures.stream().map(CompletableFuture::join).forEach(System.out::println);而且效果很好。程序执行需要1.5s,所有三个调用都同时在终端中呈现输出--一切都很好。
但当我把这个改成
HttpClient client = HttpClient.newHttpClient();
Stream.of(
"https://httpstat.us/500?sleep=1000",
"https://httpstat.us/500?sleep=1000",
"https://httpstat.us/500?sleep=1000"
).map(link -> client
.sendAsync(
newBuilder(URI.create(link)).GET().build(),
HttpResponse.BodyHandlers.discarding()
).thenApply(HttpResponse::statusCode)
).map(CompletableFuture::join).forEach(System.out::println);它似乎不再工作异步-三个500是一个接一个显示,1秒前各延迟。
为什么?我在这里错过了什么?
发布于 2022-01-10 22:50:28
这是因为Java上的map方法是一个“中间操作”,因此很懒。这意味着传递给它的Function不会在流的元素上被调用,直到下游的某个部分消耗掉该元素。
在名为JavaDoc的“流作业和管道”一节中对此进行了描述(在方括号中添加了我的注释):
中间操作返回一个新流。它们总是懒惰的;执行中间操作(如
filter()或map())实际上并不执行任何筛选或映射,而是创建一个新流,在遍历时,该流包含与给定谓词匹配的初始流的元素,或者在map()的情况下由给定的函数转换。在执行管道的终端操作之前,管道源的遍历不会开始。
在本例中,这意味着在消耗流之前不会发出请求。
在第一个示例中,collect()是消耗流的终端操作。结果是表示正在运行的请求的CompletableFuture对象的列表。
在第二个示例中,forEach是一个接一个地消耗流的每个元素的终端操作。因为join操作包含在该流中,所以每个join都会在元素传递到forEach之前完成。随后的元素将按顺序使用,因此,在先前的请求完成之前,甚至不会发出每个请求。
https://stackoverflow.com/questions/70657451
复制相似问题