CompleteableFuture异步调用
在Java中CompletableFuture用于异步编程, 是 Future API的扩展。异步编程是编写非阻塞的代码,运行的任务在一个单独的线程,与主线程隔离,并且会通知主线程它的进度,成功或者失败。
Future
Future 被用于作为一个异步计算结果的引用。提供一个 isDone()
方法来检查计算任务是否完成。当任务完成时,get()
方法用来接收计算任务的结果。
Future 的局限性
-
没有回调函数。Future提供了一个阻塞的
get()
方法通知你结果,在非阻塞的情况下,不能执行进一步操作 ,Future 不会通知你它已经完成;你无法给 Future 植入一个回调函数,当 Future 结果可用的时候,用该回调函数自动的调用 Future 的结果。 -
无法链式调用。多个 Future 不能串联在一起组成链式调用
-
不能组合多个Future。假设你有10个不同的Future,你想并行的运行,然后在它们运行未完成后运行一些函数。你会发现你也无法使用 Future 这样做。
-
没有异常处理。
-
不能手动结束。当你写了一个Future,用于通过一个远程API获取一个电子商务产品最新价格。因为这个 API 太耗时,你把它允许在一个独立的线程中,并且从你的函数中返回一个 Future。现在假设这个API服务宕机了,这时你想通过该产品的最新缓存价格手工完成这个Future 。你会发现无法这样做。
CompletableFuture 实现了 Future
和 CompletionStage
接口,并且提供了许多关于创建,链式调用和组合多个 Future 的便利方法集,而且有广泛的异常处理支持。
CompletableFuture的API
1. get、join——阻塞等待返回结果
get()
方法:阻塞等待任务结果
join()
方法:和get()
方法非常类似,会一直阻塞直到 Future 完成。这唯一不同的地方是如果最顶层的CompletableFuture完成的时候发生了异常,它会抛出一个未经检查的异常。
2. runAsync
runAsync: 执行异步任务,任务没有返回结果。
接口:CompletableFuture<Void> runAsync(Runnable runnable)
示例
CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("ending...");
});
runAsync.get();
3. supplyAsync
supplyAsync: 执行异步任务,任务有返回结果。
接口定义:
CompletableFuture<U> supplyAsync(Supplier<U> supplier)
: 函数式接口编程CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)
: 线程池执行CompletableFuture<Void> runAsync(Runnable runnable)
示例
CompletableFuture<Integer> result = CompletableFuture.supplyAsync(() -> {
int sum = 0;
for (int i = 0; i < Integer.MAX_VALUE - 1; i++) {
sum++;
}
return sum;
});
log.info("计算结果:{}", result.join());
4. thenApply(),
thenAccept() 和
thenRun()
thenApply()
, thenAccept()
和thenRun()
方法:附一个回调给CompletableFuture。
回调函数:我们使用CompletableFuture.get()
获取结果时是阻塞等待的,它会一直等到Future完成并且在完成后返回结果。当我们不想等待结果返回,我们可以把需要等待Future完成执行的逻辑写入到回调函数中。
4.1 thenApply
thenApply: 接受上一个回调结果, 继续异步任务,并返回异步执行的结果。
接口定义:CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
: Function函数式接口,接受一个T类型的参数,产出一个U类型的结果。
示例
CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
long sum = 0L;
for (int i = 0; i < 1000_000_000; i++) {
sum++;
}
log.info("step 1 ending...");
return sum;
}).thenApply((sum) -> {
Sleeper.sleep(1);
return sum;
});
log.info("计算结果:{}", future.join());
注:在thenApply()中的异步任务和上一步中的任务执行在相同的线程中
4.2 thenAccept
thenAccept: 接受上一个回调结果, 继续异步任务,不返回结果
接口定义:CompletableFuture<Void> thenAccept(Consumer<? super T> action)
示例:
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return 100;
}).thenAccept((result) -> {
log.info("the result of previous step{}", result);
}).join();
log.info("main....");
4.3 thenRun
thenRun: 不接受上个任务的回调,继续异步任务,不返回结果
接口定义:CompletableFuture<Void> thenRun(Runnable action)
CompletableFuture.supplyAsync(() -> {
Sleeper.sleep(1);
return 100;
}).thenRun(() -> {
log.info("end...");
}).join();
5. thenCompose和thenCombine
5.1 thenCompose
thenCompose:被用于当一个future依赖另外一个future的时候用来组合两个future,执行异步任务,返回结果。
接口定义:
-
CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn)
-
CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn)
: 异步执行
thenCompose看起来和thenApply类似,thenCompose和thenApply的区别是?
考虑下以下两个方法getUserDetail()
和getCreditRating()
的实现, thenApply是否能达到我们期望的结果。
CompletableFuture<User> getUsersDetail(String userId) {
return CompletableFuture.supplyAsync(() -> {
UserService.getUserDetails(userId);
});
}
CompletableFuture<Double> getCreditRating(User user) {
return CompletableFuture.supplyAsync(() -> {
CreditRatingService.getCreditRating(user);
});
}
CompletableFuture<CompletableFuture<Double>> result = getUserDetail(userId)
.thenApply(user -> getCreditRating(user));
我们预期获得的是CompletableFuture
Supplier
函数传入thenApply
将返回一个简单的值,如果在上一步中最终结果是一个嵌套的CompletableFuture。 如果你想获取最终的结果给顶层future,使用 thenCompose()
方法代替。
CompletableFuture<Double> result = getUserDetail(userId)
.thenCompose(user -> getCreditRating(user));
5.2 thenCombine
thenCombine(): 被用来当两个独立的Future都完成的时候,用来做一些事情。
接口定义:<U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
示例:
public static void main(String[] args) {
CompletableFuture<Double> future = wightKgFuture().thenCombine(heightCmFuture(), (w, h) -> {
Double heightMeter = h / 100;
return w / (heightMeter * heightMeter);
});
log.info("Your BMI is - {}", future.join());
}
public static CompletableFuture<Double> wightKgFuture() {
return CompletableFuture.supplyAsync(() ->{
Sleeper.sleep(1);
return 53.0;
});
}
public static CompletableFuture<Double> heightCmFuture() {
return CompletableFuture.supplyAsync(() ->{
Sleeper.sleep(1.5);
return 169.5;
});
}
6. allOf
allOf: 等待所有异步任务都结束之后,做一些事情
接口:CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
假设你想在一个网站的100个不同的页面上冲浪。你可以串行的做这个操作,但是这非常消耗时间。因此你想写一个函数,传入一个页面链接,返回一个CompletableFuture,异步的冲浪页面内容。
public static void main(String[] args) {
String[] urls = {"baidu.com", "google.com", "bing.com", "bingo.com"};
List<String> urlist = Arrays.asList(urls);
List<CompletableFuture<String>> futureList = urlist.stream()
.map(AllOfTest_9::surfing)
.collect(Collectors.toList());
//把List<CompletableFuture<String>> -> List<String>
List<String> result = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]))
.thenApply(v -> futureList.stream().map(CompletableFuture::join).collect(Collectors.toList()))
.join();
log.info("{}", result);
}
private static CompletableFuture<String> surfing(String url) {
return CompletableFuture.supplyAsync(() -> {
Sleeper.sleep(1);
return "www." + url;
});
}
7. anyOf
anyOf: 任意一个异步任务都结束之后,做一些事情
接口:CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
假如Jackson、Harry和David在赛跑,获取谁是冠军。
public static void main(String[] args) {
Object join = CompletableFuture.anyOf(Jackson(), Hayley(), David()).join();
log.info("{}", join);
}
public static CompletableFuture<String> Jackson() {
return CompletableFuture.supplyAsync(() ->{
Sleeper.sleep(2);
return "Jackson win";
});
}
public static CompletableFuture<String> Hayley() {
return CompletableFuture.supplyAsync(() ->{
Sleeper.sleep(1);
return "Harry win";
});
}
public static CompletableFuture<String> David() {
return CompletableFuture.supplyAsync(() ->{
Sleeper.sleep(2);
return "David win";
});
}
8. CompletableFuture异常处理
首先让我们明白在一个回调链中错误是怎么传递的。思考下以下回调链:
CompletableFuture.supplyAsync(() -> {
// Code which might throw an exception
return "Some result";
}).thenApply(result -> {
return "processed result";
}).thenApply(result -> {
return "result after further processing";
}).thenAccept(result -> {
// do something with the final result
});
如果在原始的supplyAsync()
任务中发生一个错误,这时候没有任何thenApply
会被调用并且future将以一个异常结束。如果在第一个thenApply
发生错误,这时候第二个和第三个将不会被调用,同样的,future将以异常结束。
8.1 exceptionally
exceptionally()回调处理异常:提供一个回调函数,给你一个从原始Future中生成的错误恢复的机会。你可以在这里记录这个异常并返回一个默认值。
public static void main(String[] args) {
int age = 0;
CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> {
Sleeper.sleep(1);
if (age == 0) {
throw new IllegalArgumentException("Age can not be negative");
}
return "OK";
}).exceptionally(ex -> {
log.warn("Oops! We have an exception - {}", ex.getMessage());
return "No";
});
log.info("{}", result.join());
}
8.2 handle
Handle: 处理异常, 无论一个异常是否发生它都会被调用, 处理异常,返回一个默认值。
public static void main(String[] args) {
int a = 0;
CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> {
Sleeper.sleep(1);
if (a == 0) {
throw new RuntimeException("Age can not be negative");
}
return "OK";
}).handle((res, ex) -> {
if (ex != null) {
log.warn("Oops! We have an exception - {}", ex.getMessage());
res = "NO";
}
return res;
});
log.info("{}", result.join());
}