这个问题有两个层面:
第一个层面,从业务上来看,某一个业务逻辑能拆分成多个任务,然后如何异步执行这些任务。
这个可以用CompletableFuture来解决,比如业务逻辑拆分
public class Main {
private static CompletableFuture task1() {
//CompletableFuture内部可以放业务逻辑,比如可以sleep 1s return CompletableFuture.completedFuture("task1");
}
private static CompletableFuture task2() {
return CompletableFuture.completedFuture("task2");
}
public static void main(String[] args) {
// thenCombine表示将两个CompletableFuture的结果“组合”起来 // 还可以看下CompletableFuture的thenXXX系列方法 task1().thenCombine(task2(), (res1, res2) -> {
// 组装结果 return res1 + "," + res2;
}).thenAccept(res -> {
// 输出最终结果 System.out.println(res);
}).join();
}
}
第一,这儿的CompletableFuture仅仅是一个逻辑上的接口,具体的实现可以用线程池、用IO多路复用啥的,这个后面说。
第二,CompletableFuture仅仅是Promise的一个实现,还是会“破坏”调用栈,但是在loom出现之前,这个是比较好的方案。(如果loom出现,大概率还是要用CompletableFuture,只是线程池可以用的更加大胆了而已。)
第三,reactor的Mono等,也是类似的原理。
第二个层面,就是具体实现多个任务的问题;具体到题目中的场景,就是如何并发地完成多个IO任务,这个就是IO多路复用要解决的问题。你需要一个异步的HttpClient,netty算一个,java9之后jdk也自带了一个HttpClient。
示例代码:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.CompletableFuture;
public class Main {
private static HttpClient httpClient = HttpClient.newHttpClient();
private static CompletableFuture api1() {
HttpRequest req = HttpRequest.newBuilder(URI.create("https://httpbin.org/delay/10"))
.GET()
.build();
return httpClient.sendAsync(req, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body);
}
private static CompletableFuture api2() {
HttpRequest req = HttpRequest.newBuilder(URI.create("https://httpbin.org/delay/10"))
.GET()
.build();
return httpClient.sendAsync(req, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body);
}
public static void main(String[] args) {
Instant started = Instant.now();
api1().thenAcceptBoth(api2(), (res1, res2) -> {
System.out.println(res1);
System.out.println(res2);
}).thenRun(() -> {
System.out.println(
Duration.between(started, Instant.now()).toMillis()
);
}).join();
}
}
可以看到,两个delay 10s的api,总共用了12s(我本地的结果),而且也没有线程池。
当然,如果是要进行其他CPU密集的操作,用线程池来做会好一点,但是对于使用方而言,还是一个CompletableFuture接口而已。
okhttp是采用回调的,但是也可以方便的转成CompletableFuture接口。
一点吐槽:
jdk带了HttpClient,但是HttpReuqest只能接受URI,然后没有URIBuilder,这意味着url中的GET参数只能自己一个一个拼上去,非常繁琐。