CompletableFuture
CompletableFuture是函数式异步编程工具,针对Future的一些缺点做的改进,可以通过回调函数的方式实现异步编程。以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法进行异步任务编排以及通用的异常处理机制。
Future的缺点:
- Future必须阻塞主线程 or 主线程轮询获取结果
- Future无法很好的实现异步任务间的复杂编排(比如前后依赖、等待全部完成、任一任务完成得到通知等),很难实现纯异步编程
- 稍复杂的场景使用线程池 + Future 代码可读性更低
CompletableFuture 基本使用
创建CompletableFuture对象提交异步任务
创建一个CompletableFuture对象即创建提交异步任务,创建CompletableFuture对象的方法如下:
- 静态辅助方法 CompletableFuture.completedFuture
CompletableFuture.completedFuture是一个静态辅助方法,用来返回一个已经计算好的CompletableFuturepublic static <U> CompletableFuture<U> completedFuture(U value)
- 以下四个静态方法用来为一段异步执行的代码创建CompletableFuture对象
以Async结尾并且没有指定Executor的方法会使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,线程池默认大小=cpu核数。-
runAsync: 以Runnable函数式接口类型为参数,所以CompletableFuture计算结果为空
// 使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,线程池默认大小=cpu核数 public static CompletableFuture<Void> runAsync(Runnable runnable) // 使用指定的 executor作为它的线程池执行异步代码 public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
-
supplyAsync: 以Supplier函数式接口类型为参数,CompletableFuture计算结果类型为U
// 使用默认ForkJoinPool.commonPool()作为线程池 public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) // 使用制定的executor作为线程池 public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
-
例子:
因为方法的参数类型都是函数式接口,所以可以使用lambda表达式实现异步任务,比如:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
//长时间的计算任务
return "·00";
});
获取结果
同步阻塞获取异步结果(不推荐):
CompletableFuture类实现了CompletionStage和Future接口,所以你还是可以像以前一样通过阻塞或者轮询的方式获得结果
public T get()
// 阻塞等待指定时间,超时后抛出TimeoutException
public T get(long timeout, TimeUnit unit)
//如果结果已经计算完则返回结果或者抛出异常,否则返回给定的valueIfAbsent值
public T getNow(T valueIfAbsent)
// 一直阻塞,直到任务处理结束有结果返回或抛出一个unchecked异常(CompletionException)
public T join()
例子:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
int i = 1/0;
return 100;
});
// 分别调用join和get 看两种对抛出异常的处理的区别
//future.join();
future.get();
主动完成计算
当future在本身线程执行,没有关联任何的callback、线程池、异步任务等,如果客户端调用future.get就会一直等下去。 因此我们可以完成主动的计算,触发客户端:
// 主动完成计算,触发客户端的等待
f.complete(100);
// 抛出一个异常
f.completeExceptionally(new Exception());
CompletableFuture.complete()、CompletableFuture.completeExceptionally只能被调用一次。但是我们有两个后门方法可以重设这个值:obtrudeValue、obtrudeException,但是使用的时候要小心,因为complete已经触发了客户端,有可能导致客户端会得到不期望的结果。
例子:
public class BasicMain {
public static CompletableFuture<Integer> compute() {
final CompletableFuture<Integer> future = new CompletableFuture<>();
return future;
}
public static void main(String[] args) throws Exception {
final CompletableFuture<Integer> f = compute();
class Client extends Thread {
CompletableFuture<Integer> f;
Client(String threadName, CompletableFuture<Integer> f) {
super(threadName);
this.f = f;
}
@Override
public void run() {
try {
System.out.println(this.getName() + ": " + f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
new Client("Client1", f).start();
new Client("Client2", f).start();
System.out.println("waiting");
f.complete(100);
//f.completeExceptionally(new Exception());
System.in.read();
}
}
计算结果完成时的处理
返回原始cf的计算结果
当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的Action。主要是下面的方法:
这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常。
方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。
Action的类型是BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况。