CompletableFuture的基本使用
Future的缺陷
1.无法手动完成:
假设您已经编写了一个函数,用于从远程API获取电子商务产品的最新价格。由于此API调用非常耗时,因此您需要在单独的线程中运行它,并从函数中返回Future。
假设远程API服务已关闭,那么您想按产品的最后缓存价格手动完成Future。但通过Future是做不到的。
2.在非阻止的情况下,无法对Future的结果执行进一步的操作
Future不会将完成通知您。它提供了一个get()方法,该方法将阻塞直到结果可用为止。然而,无法将回调函数附加到Future上,并且无法在Future的结果可用时自动调用它。
3.多个Futures不能链接在一起:
有时您需要执行一个时间很长的计算过程,当计算完成时,您需要将其结果发送到另一个长时间的计算过程,依此类推。
无法使用Future创建此类异步工作流。
4.不能将多个Futures组合在一起:
假设我们要并行运行10个不同的Future,在它们全部完成后再执行某些功能。
使用Future,也无法做到这一点。
CompletableFuture介绍
CompletableFuture并没有改变原有Future本身的行为,它实现了Future和CompletionStage接口,并提供了大量方便的方法来创建,链接和组合多个Future。另外,它还具有非常全面的异常处理支持。
使用CompletableFuture可以创建一个任务链,当当前任务的结果可用时,将触发下一个任务。
基本特性的理解
永远阻塞时返回默认值
若在main函数中执行以下两行代码,则会永远阻塞。
CompletableFuture<String> completableFuture = new CompletableFuture<>();
String result = completableFuture.get();
这是由于创建新的CompletableFuture时是非完成状态的,而get是当CompletableFuture完成时才取回结果。所以会一直阻塞。
我们可以手动的改变完成时的返回值,就是说:当任务没有完成时,可以手动的设置一个默认的特殊返回值。可以参考以下代码:
CompletableFuture<String> completableFuture = new CompletableFuture<>();
completableFuture.complete("A seted result");
String result = completableFuture.get();
System.out.println(result);
以上代码不会阻塞,而是直接返回了,并得到手动设置的结果。
异步执行任务返回CompletableFuture:runAsync函数
若希望任务在后台执行,且不返回任何结果,可以使用runAsync函数来执行任务。该函数在ForkJoinPool.commonPool()
中执行任务,并返回一个CompletableFuture。
// 异步执行一个Runnable对象
CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
// Simulate a long-running Job
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
System.out.println("hello world.");
}
});
// 阻塞,等待future的返回
future.get()
从以上代码可以看到,通过runAsync返回的是一个CompletableFuture对象。并没有返回任何的结果对象,也不能通过future来获取返回结果。
异步执行任务并返回结果:supplyAsync函数
- 使用常规表达式编写任务代码
// 通过定义Supplier对象来运行特定任务,并返回结果
CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "supplyAsync result!";
}
});
// 阻塞,并等待结果的返回
String result = future.get();
System.out.println(result);
- 使用lamda表达式编写任务代码
// Using Lambda Expression
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// Simulate a long-running Job
try {
TimeUnit.SECONDS.sleep(1);
return "supplyAsync result!";
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
});
String r = future.get();
System.out.println(r);
通过线程池来执行以上任务
runAync和supplyAsync的接口也支持使用已有线程池来执任务。以下代码使用已经创建好的线程池中的某个线程来执行任务:
Executor executor = Executors.newFixedThreadPool(10);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return Thread.currentThread().getName() + " supplyAsync result";
}, executor);
String res = future.get();
System.out.println(res);
通过回调函数处理CompletableFuture的结果
CompletableFuture.get()方法被阻塞。它等待,直到Future完成,并在其完成后返回结果。但是,有时候这不是我们想要?对于构建异步系统,我们应该能够将回调附加到CompletableFuture上,当Future完成时,该回调应自动被调用。这样,我们就不必等待结果了,我们可以在Future函数内部编写完成Future之后需要执行的逻辑。
您可以使用thenApply(),thenAccept()和thenRun()方法将回调函数附加到CompletableFuture上。
1. thenApply()
可以使用thenApply()方法来处理和转换CompletableFuture的结果。它以Function <T,R>作为参数。 Function <T,R>是一个简单的函数接口,表示一个函数,该函数接受类型T的参数并产生类型R的结果-
// Create a CompletableFuture
CompletableFuture<String> whatsYourNameFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "supplyAsync result";
});
// 通过thenApply()来附加一个回调函数到Future
CompletableFuture<String> greetingFuture = whatsYourNameFuture.thenApply(name -> {
return "Hello " + name;
});
// 阻塞并获取future的结果
System.out.println(greetingFuture.get()); // Hello Rajeev
- 另一种写法
可以通过级联的写法。
CompletableFuture<String> welcomeText = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "supplyAsync result";
}).thenApply(name -> {
return "Hello " + name;
}).thenApply(greeting -> {
return greeting + ", Welcome to here.";
});
System.out.println(welcomeText.get());
2.thenAccept() and thenRun()
如果不想从回调函数中返回任何内容,而只想在Future完成后运行一段代码,则可以使用thenAccept()和thenRun()方法。这些方法是使用者,通常用作回调链中的最后一个回调。
CompletableFuture.thenAccept()接受Consumer 并返回CompletableFuture 。它可以访问其所附加的CompletableFuture的结果。
// thenAccept()例子
CompletableFuture.supplyAsync(() -> {
return OderService.getOrderDetail(orderId);
}).thenAccept(order -> {
System.out.println("得到的订单细节: " + order.getName())
});
thenAccept()可以访问其所连接的CompletableFuture的结果,而thenRun()甚至都不能访问Future的结果。它需要一个Runnable并返回CompletableFuture 。
// thenRun() 例子
CompletableFuture.supplyAsync(() -> {
// 运行一些计算代码
}).thenRun(() -> {
// 运行完成的代码
});
小结
本文介绍了CompletableFuture的基本概念和基本用法。通过CompletableFuture可以让任务异步执行,并可以设置自动处理执行结果,比Future更加方便。