一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度。JDK5新增了Future
接口,用于描述一个异步计算的结果。虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,我们必须使用Future.get()
的方式阻塞调用线程,或者使用轮询方式判断 Future.isDone
任务是否结束,再获取结果。
与此同时,Future无法解决多个异步任务需要相互依赖的场景,简单点说就是,主线程需要等待子线程任务执行完毕之后在进行执行,这个时候你可能想到了CountDownLatch
,没错确实可以解决。
通过CompletableFuture
可以很轻松的实现CountDownLatch
的功能。比如可以实现 :任务1执行完了再执行任务2,甚至任务1执行的结果,作为任务2的入参数等等强大功能。
//使用默认内置线程池ForkJoinPool.commonPool(),根据supplier构建执行任务
//自定义线程,根据supplier构建执行任务
//使用默认内置线程池ForkJoinPool.commonPool(),根据runnable构建执行任务
//自定义线程,根据runnable构建执行任务
在Future中就已经提供了,继承了原有获取结果方式
也在Future中就已经提供了,在上述的情况下添加超时处理,如果在指定时间内未获取结果将抛出超时异常
立即获取结果不阻塞,结果计算已完成将返回结果或计算过程中的异常,如果未计算完成将返回设定的valueIfAbsent值
**用于等待异步任务完成并获取结果的方法。**如果异步任务已经完成,则该方法会立即返回任务的执行结果;如果异步任务尚未完成,则该方法会阻塞当前线程,直到任务执行完成并返回结果为止。
**做完第一个任务后,再做第二个任务,第二个任务也没有返回值。**如果你执行第一个任务的时候,传入了一个自定义线程池:
thenRunAsync
执行第二个任务时,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池。说明:
后面介绍的thenAccept
和thenAcceptAsync
,thenApply
和thenApplyAsync
等,它们之间的区别也是这个。
第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参 ,传递到回调方法中,但是回调方法是没有返回值的。
表示第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,并且回调方法是有返回值的。
当CompletableFuture
的任务不论是正常完成还是出现异常它都会调用 「whenComplete」 这回调函数。
即调用get()
时,正常完成时就获取到结果,出现异常时就会抛出异常,需要你处理该异常。
@Test
public void testWhenCompleteExceptionally() throws ExecutionException, InterruptedException {
CompletableFuture<Double> future = CompletableFuture.supplyAsync(() -> {
if (Math.random() < 0.5) {
throw new RuntimeException("出错了");
}
System.out.println("正常结束");
return 0.11;
}).whenComplete((aDouble, throwable) -> {
if (aDouble == null) {
System.out.println("whenComplete aDouble is null");
} else {
System.out.println("whenComplete aDouble is " + aDouble);
}
if (throwable == null) {
System.out.println("whenComplete throwable is null");
} else {
System.out.println("whenComplete throwable is " + throwable.getMessage());
}
}).exceptionally((throwable) -> {
System.out.println("exceptionally中异常:" + throwable.getMessage());
return 0.0;
});
System.out.println("最终返回的结果 = " + future.get());
}
当出现异常时,exceptionally
中会捕获该异常,给出默认返回值0.0。
thenCombine
/ thenAcceptBoth
/ runAfterBoth
都表示:「当任务一和任务二都完成再执行任务三」 。
区别在于:
applyToEither
/ acceptEither
/ runAfterEither
都表示:「两个任务,只要有一个任务完成,就执行任务三」 。
区别在于:
CompletableFuture
的get()
方法是阻塞的,如果使用它来获取异步调用的返回值,需要添加超时时间。
CompletableFuture
代码中又使用了默认的 「ForkJoin线程池」 ,处理的线程个数是电脑 「CPU核数-1」 。在大量请求过来的时候,处理逻辑复杂的话,响应会很慢。一般建议使用自定义线程池,优化线程池配置参数。
CompletableFuture
的get()方法是阻塞的,我们一般建议使用future.get(5, TimeUnit.SECONDS)
。并且一般建议使用自定义线程池。
但是如果线程池拒绝策略是DiscardPolicy
或者DiscardOldestPolicy
,当线程池饱和时,会直接丢弃任务,不会抛出异常。因此建议,CompletableFuture
线程池策略最好使用AbortPolicy
,然后耗时的异步线程,做好线程池隔离。
//CompletableFuture类结构
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
volatile Object result; // Either the result or boxed AltResult
volatile Completion stack; // Top of Treiber stack of dependent actions
...
}
//stack属性栈结构
abstract static class Completion extends ForkJoinTask<Void>
implements Runnable, AsynchronousCompletionTask {
volatile Completion next; // Treiber stack link
...
}
**result:**存储CompletableFuture的返回,或者存储异常对象AltResult。
**stack:**是CompletableFuture.Completion对象,表示操作数栈栈顶,在进行CompletableFuture链式调用的过程中,所有链式调用的CompletableFuture任务都会被压入该stack中,在任务调用的过程按后进先出的顺序出栈执行完所有任务。
CompletableFuture在执行任务时会创建出新的CompletableFuture对象,使用新对象执行任务并获取结果使用CAS写入到result属性。如果任务未执行将压入栈顶,再重新尝试任务执行,在CompletableFuture其他方法的调用也都大同小异。
CompletableFuture异步调用则要使用Async
结尾的方法执行任务。主要是通过将任务压入栈顶后,使用tryFire方法进行异步处理,如果任务未被执行,则会通过postFire方法,由线程池中的线程进行任务执行,任务执行结果再使用CAS将结果返回到result。其他线程即可得知任务是否被执行过,如果当前现场判断当前任务为被执行,则调用postComplete()执行完成任务。
CompletableFuture
通过异步回调的方式,解决了开发过程中异步调用获取结果的难点。开发者只需接触到CompletableFuture
对象,以及CompletableFuture
任务的执行结果,无需设计具体异步回调的实现,并可通过自定义线程池进一步优化任务的异步调用。