Future
代表异步执行的挂起的结果。它的方法get
,在执行计算后返回执行结果。由此产生问题:get
方法调用是阻塞的,直到异步计算结束。由此造成异步计算起不到“异步”的效果。你可以在job中实现所有场景,再发送到executor
中,但是为什么要为错综复杂的逻辑编写同样错综复杂的执行步骤呢。因此,你需要CompletableFuture
CompletableFuture
实现Future
接口外,它还实现了CompletionStage
接口,CompletionStage
接口保证了计算最终会被执行。CompletionStage
的最大优点是,提供了多种方法,用于执行结束时回调(callbacks)。由此构建非阻塞的系统。
最简单的异步计算
创建异步计算:
CompletableFuture.supplyAsync(this::sendMsg);
supplyAsync
方法有一个Supplier
类作为参数,该类包含了需要异步执行的代码,这个例子中是sendMsg
方法。像Future
一样,可以提供Executor
作为第二个参数,如果缺省,Supplier
会被提交给ForkJoinPool.commonPool()
添加回调
回调的意义是:当异步计算结束时,执行回调的代码,而不用一直等待执行结束。上例中,通过sendMsg
方法异步发送一个消息,接下来,我们添加回调代码,用于通知发送消息执行的结果。
CompletableFuture.supplyAsync(this::sendMsg).thenAccept(this::notify);
thenAccept
是诸多添加回调的一种方法。参数是Consumer
,本例中是notify
,用于计算执行结束时处理结果。
级联多个回调
如果想持续的在多个回调之间传递值,thenAccept
不能用,因为Consumer
不返回任何东西。一直传值,可以用thenApply
。
thenApply
的参数Function
,接受一个值,并且返回一个值。
CompletableFuture.supplyAsync(this::findReceiver).thenApply(this:sendMsg).thenAccept(this::notify);
现在异步的任务是,首先找到接收者,然后发送消息到接收器,最后发送消息的结果发给notify。
构建异步系统
构建更大的异步系统,你很可能想创建新的代码段,依赖小的代码段。这些大小代码段都是异步的,在我们的例子中返回CompletionStage。到目前为止,sendMsg都是一个阻塞函数,假设有个返回CompletionStage的sendMsgAsync函数,如果像上面例子一直使用thenApply,最终我们会得到一堆嵌套的CompletionStage。
CompletableFuture.supplyAsync(this::findReceiver).thenApply(this::sendMsgAsnc);
// returns type: CompletionStage<CompletionStage<String>>
而我们不想要嵌套的CompletionStage,所以我们可以用thenCompose,它接受Function参数,返回CompletionStage。就像flatMap实现的扁平效果一样:
CompletableFuture.supplyAsync(this::findReceiver).thenCompose(this::sendMsgAsync);
// returns type CompletionStage<String>
用async结尾的回调分离的任务
到目前为止,所有的回调都在它前一个执行单元的线程中执行。可以把回调各自提交到ForkJoinPool.commonPool(),而不是使用前一个执行单元的线程,这是通过使用CompletionStage提供的以async后缀的方法实现的。
发送两个报文到相同的接收器:
CompletableFuture<String> reciever = CompletableFuture.supplyAsync(this::findReceiver);
reciever.thenApply(this::sendMsg);
reciever.thenApply(this::sendOtherMsg);
上面例子中,所有的Function
都在同一个线程中执行,导致后一个消息等待前一个消息执行完。
CompletableFuture<String> reciever = CompletableFuture.supplyAsync(this::findReciever);
receiver.thenApplyAsync(this::sendMsg);
reciever.thenApplyAsync(this::sendMsg);
上面的例子,每个发送消息都被独立的提交给ForkJoinPool.commonPool()
。结果是两个sendMsg
回调在异步计算执行结束时都会执行。
关键是:异步版本的方法,在你有几个回调函数,而它们又依赖相同计算结果的时,非常方便有效。
异常的处理
如果你用过Future
,就会知道糟糕的时候有多糟糕。幸运的是,CompletableFuture
有一个漂亮的对应手段,通过使用exceptionally
。
CompletableFuture.supplyAsync(this::failingMsg).exceptionally(ex->new Result(Status.FAILED)).thenAccept(this::notify);
exceptionally
给我们一个机会恢复,通过执行当异步执行的计算抛出exception
时备选的方法(alternative method)。而其他的回调可以继续执行,它们的输入是alternative method的返回值。更复杂的差错处理,可以用whenComplete
和handle
。
基于多个计算结果的回调
创建依赖多个计算结果的回调,用thenCombine
,它允许注册BiFunction
类回调,依赖两个CompletionStage
。
CompletableFuture<String> to = CompletableFuture.supplyAsync(this::findReceiver);
CompletableFuture<String> text = CompletableFuture.supplyAsync(this::createContent);
to.thenCombine(text, this::sendMsg);
值得一提的是,thenCombine
有一个变种,runAfterBoth
,它不关心前面的计算结果,只要它们执行完就够了,它有一个Runnable
参数。
依赖于至少一个计算结果的回调
发送消息有两个消息源,任何一个源执行完毕就可以发送消息。
CompletableFuture<String> firstSource = CompletableFuture.supplyAsync(this::findByFirstSource);
CompletableFuture<String> secondSource = CompletableFuture.supplyAsync(this::findBySecondSource);
firstSource.acceptEither(secondSource, this::sendMsg);
通过acceptEither
实现,它接受两个等待中的计算,以及一个Consumer
,后者在两个计算中任意一个执行结束后执行。
例子
@Scheduled(fixedRate = 2000)
public void test() {
log.info("begin: {}", new Date());
ActionService actionService = new ActionService();
CompletableFuture<Void> future = CompletableFuture.supplyAsync(actionService::test1)
.thenApply(actionService::test2)
.thenApply(actionService::test3)
.thenAccept(actionService::test4);
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
log.info("end: {}", new Date());
}
public Integer test1() {
log.info("{}, {}", Thread.currentThread().getName(), new Date());
try {
Thread.sleep(1000l);
} catch (InterruptedException e) {
log.error(e.getMessage());
}
return 1;
}
运行结果: