文章目录
前言
在JDK1.8之前,我们使用的Java多线程编程,主要创建方式有两种,继承Thread类或者实现Runnable接口,但是这两种方式有个弊端,无法获取到线程执行的结果。今天和大家分享在JDK1.8中提供的一种更加强大的异步编程类CompletableFuture,来解决之前的疼点问题。
一、CompletableFuture概述
CompletableFuture提供了更加强大的异步编程api,它实现了Future接口和CompletionStage接口,是对前者的丰富扩展,弥补了获取执行结果的局限性;同时CompletionStage实现了对任务的编排能力。
CompletableFuture相比于Future最大的改进就是提供了回调监听的功能,轻松的组织不同任务的执行顺序和执行方式,可以将上一任务的执行结果作为下一任务的条件,很方便的获取每一次任务的执行结果,在某种程度上讲,这项能力是它的核心能力。
二、CompletableFuture的常用api
1. 创建异步任务
1.1 supplyAsync
supplyAsync是创建带有返回值的异步任务。它有如下两个方法,一个是使用默认线程池(ForkJoinPool.commonPool())的方法,一个是带有自定义线程池的重载方法
// 带返回值异步请求,默认线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
// 带返回值的异步请求,可以自定义线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
1.2 runAsync
runAsync是创建没有返回值的异步任务。它有如下两个方法,一个是使用默认线程池(ForkJoinPool.commonPool())的方法,一个是带有自定义线程池的重载方法
// 不带返回值的异步请求,默认线程池
public static CompletableFuture<Void> runAsync(Runnable runnable)
// 不带返回值的异步请求,可以自定义线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
ForkJoinPool线程池默认创建的线程数是 CPU 的核数。如果所有CompletableFuture共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的线程池,以避免互相干扰
2. 获取任务执行结果
2.1 get
用来获取CompletableFuture异步之后的返回值,抛出的是经过检查的异常。
// 如果完成则返回结果,否则就抛出具体的异常
public T get() throws InterruptedException, ExecutionException
// 最大时间等待返回结果,否则就抛出具体异常
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
2.2 join
用来获取CompletableFuture异步之后的返回值,抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。
// 完成时返回结果值,否则抛出unchecked异常。为了更好地符合通用函数形式的使用,如果完成此 CompletableFuture所涉及的计算引发异常,则此方法将引发unchecked异常并将底层异常作为其原因
public T join()
3. 异步回调处理
3.1 thenApply和thenApplyAsync
thenApply 表示某个任务执行完成后执行的动作,即回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中。
thenAccep和thenAccepAsync区别在于,使用thenAccep方法时子任务与父任务使用的是同一个线程,而thenAccepAsync在子任务中可能是另起一个线程执行任务,并且thenAccepAsync可以自定义线程池,默认的使用ForkJoinPool.commonPool()线程池。
3.2 thenAccept和thenAcceptAsync
thenAccep表示某个任务执行完成后执行的动作,即回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中,无返回值。
thenAccep和thenAccepAsync区别在于,使用thenAccep方法时子任务与父任务使用的是同一个线程,而thenAccepAsync在子任务中可能是另起一个线程执行任务,并且thenAccepAsync可以自定义线程池,默认的使用ForkJoinPool.commonPool()线程池。
3.3 thenRun和thenRunAsync
thenRun表示某个任务执行完成后执行的动作,即回调方法,无入参,无返回值。
thenRun和thenRunAsync区别在于,使用thenRun方法时子任务与父任务使用的是同一个线程,而thenRunAsync在子任务中可能是另起一个线程执行任务,并且thenRunAsync可以自定义线程池,默认的使用ForkJoinPool.commonPool()线程池。
4. 多任务组合处理
4.1 allOf
用来实现多任务的并行执行,当所有任务完成时返回一个新的CompletableFuture。经常结合get或join方法使用,阻塞主线程,主线程需要等待所有任务异步执行完毕,才会继续往下执行。只要有一个任务执行异常,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回null。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("future1完成!");
return "future1完成!";
});
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
System.out.println("future2完成!");
});
//将创建的所有异步任务加入到CompletableFuture中
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
//阻塞主线程,等待全部异步任务执行完毕后唤醒主线程
all.join();
4.2 anyOf
用来实现多任务的并行执行,当其中某一个任务完成时返回一个新的CompletableFuture。经常结合get或join方法使用,阻塞主线程,主线程只要等待所有任务的其中一个执行完毕,就会继续往下执行。如果其中一个任务抛出异常,不会影响anyOf的执行,如果都是正常执行,则get返回执行完成任务的结果。
三、CompletableFuture使用需要注意点
因为使用CompletableFuture的程序相当于引入了多线程或异步,我们首先需要想到的是当前task由哪个Thread来执行,使用不当容易引起性能问题,可以根据方法命名做区分:
xxxx():表示该方法将继续在当前执行CompletableFuture的方法线程中执行
xxxxAsync():表示异步,在线程池的其它线程中执行
结束语
本文只列举了CompletableFuture类中一少部分比较常用的api方法,这些方法都是在生产中实际用过的,还有很多可以用在不同场景下的方法,朋友们可以去Java官方中文API文档中自行研究。
简单的分享,希望能帮助更多初学者,同时欢迎大家的指正和交流。如果觉着本文有用的话,记着点赞和收藏!