Preface
CompletableFuture
是 jdk8 的新特性。CompletableFuture 实现了 CompletionStage 接口和Future 接口,前者是对后者的一个扩展,增加了异步会点、流式处理、多个 Future 组合处理的能力,使 Java 在处理多任务的协同工作时更加顺畅便利。
在以下的例子中会使用到 SmallTool 这个工具类,它有两个静态方法:一个是睡眠指定毫秒;一个是打印当前时间和线程信息。
创建异步任务
CompletableFuture 创建异步任务的方法有 2 类共 4 种:
supplyAsync
supplyAsync
用来创建带有返回值的异步任务,它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
例子:
使用默认线程池:
使用自定义线程池:
runAsync
runAsync
用来创建没有返回值的异步任务,它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
获取执行结果
获取执行结果有
get()
和join方法
,它们都是阻塞式获取。
get
get()
方法是阻塞的,调用它会等待CompletableFuture
的计算完成,并返回计算的结果。如果计算还未完成,调用该方法会导致当前线程阻塞。如果计算过程中发生异常,异常会被包装为ExecutionException
并在调用get()
方法时抛出。
join
join()
方法与get()
方法类似,但不会抛出受检异常。如果计算过程中发生异常,异常会被包装为CompletionException
并在调用join()
方法时抛出。由于不抛出受检异常,join()
在某些情况下更加方便。
串联异步操作
把不同的任务前后连接起来。
thenCompose / thenComposeAsync
thenCompose()
用于将两个异步操作串联起来,它的作用是在一个操作完成后,将其结果传递给另一个操作,并返回一个新的CompletableFuture
对象,表示串联后的异步操作。
具体来说,thenCompose()
方法用于处理异步操作的"链式调用",其中第一个操作的结果会作为参数传递给第二个操作,从而实现操作的顺序执行。这种组合方式可以避免嵌套的回调地狱,使异步操作的代码更加清晰和易于理解。
例子:
thenComposeAsync()
与thenCompose()
类似,用于将两个异步操作串联起来。不同的是thenComposeAsync()
方法会在指定的执行器(线程池)中执行操作,从而实现异步操作的并行执行。
具体来说,thenComposeAsync()
方法的作用是将第一个操作的结果传递给第二个操作,并返回一个新的CompletableFuture
对象,表示串联后的异步操作。不同之处在于,第二个操作会在指定的执行器(线程池)中执行,而不会阻塞当前线程。
它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
例子:
组合异步操作
把不同的任务前后组合起来。
thenCombine / thenCombineAsync
thenCombine()
用于组合两个独立的异步操作的结果,然后将这两个结果传递给一个函数进行处理,并返回一个新的CompletableFuture
对象,表示组合后的异步操作。
具体来说,thenCombine()
方法的作用是将两个独立的异步操作的结果合并,然后使用一个函数对这两个结果进行处理。这个函数可以是一个BiFunction
,它接受两个参数,分别是两个操作的结果,然后返回一个新的结果。返回的结果会作为新的CompletableFuture
的计算结果。
例子:
thenCombineAsync()
类似于thenCombine()
,用于将两个独立的异步操作的结果合并,并在指定的执行器(线程池)中执行合并操作。
具体来说,thenCombineAsync()
方法的作用是将两个独立的异步操作的结果合并,然后通过一个函数对这两个结果进行处理。这个函数可以是一个BiFunction
,它接受两个参数,分别是两个操作的结果,然后返回一个新的结果。返回的结果会作为新的CompletableFuture
的计算结果。
它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
例子:
thenAcceptBoth / thenAcceptBothAsync
thenAcceptBoth
方法的作用是在两个CompletableFuture
都完成时,对它们的结果进行操作。与此同时,它不返回任何结果。
具体来说,thenAcceptBoth
方法接受两个参数:另一个CompletableFuture
对象和一个消费者(Consumer)函数。这个消费者函数会在两个CompletableFuture
都完成时被调用,传入这两个任务的结果作为参数。
例子:
thenAcceptBothAsync
方法的作用与thenAcceptBoth
类似,但是允许你在两个CompletableFuture
都完成时,以异步的方式执行一个操作,不会阻塞当前线程。
具体来说,thenAcceptBothAsync
方法接受两个参数:另一个CompletableFuture
对象和一个消费者(Consumer)函数。这个消费者函数会在两个CompletableFuture
都完成时以异步的方式被调用,传入这两个任务的结果作为参数。
它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
例子:
runAfterBoth / runAfterBothAsync
runAfterBoth
方法的作用是在两个CompletableFuture
都完成时,执行一个操作,但不关心任务的结果,也不返回任何结果。它类似于执行一个后续的清理或结束操作。
具体来说,runAfterBoth
方法接受两个参数:另一个CompletableFuture
对象和一个Runnable
接口的实例。这个Runnable
对象会在两个CompletableFuture
都完成时被调用,执行其中的操作。
例子:
runAfterBothAsync
方法的作用与runAfterBoth
类似,但是允许你在两个CompletableFuture
都完成时,以异步的方式执行一个操作,不会阻塞当前线程。
它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
例子:
处理结果
处理前一个任务的执行情况。
thenApply / thenApplyAsync
thenApply()
用于在异步操作完成后对其结果进行处理,并返回一个新的CompletableFuture
对象,表示处理后的结果。
具体来说,thenApply()
方法的作用是在当前异步操作完成后,应用一个函数(Function
)来处理操作的结果,并将处理后的结果作为新的CompletableFuture
的计算结果。
相当于 Stream Api 中的map
操作。
例子:
thenApplyAsync()
与thenApply()
类似,用于在异步操作完成后对其结果进行处理。然而,thenApplyAsync()
具有并行执行操作的能力,可以在指定的执行器中执行处理操作,从而实现异步操作的并行处理。
具体来说,thenApplyAsync()
方法的作用是在当前异步操作完成后,使用一个函数(Function
)来处理操作的结果,并在指定的执行器中执行处理操作,然后返回一个新的CompletableFuture
对象,表示处理后的结果。
它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
例子:
thenAccept / thenAcceptAsync
thenAccept
作用是在一个CompletableFuture
完成时,对其结果进行处理,但不返回任何结果。换句话说,它允许你在异步操作完成后执行一个处理结果的操作。
具体来说,thenAccept
方法接受一个参数,该参数是一个消费者(Consumer)函数。这个函数会在前一个CompletableFuture
完成时被调用,并传入前一个CompletableFuture
的结果作为参数,从而让你能够对结果进行处理。
thenAccept
方法适用于那些你只需要处理异步任务结果而不需要返回值的情况,它可以让你在异步任务完成时执行一些操作,如日志记录、结果处理等。
例子:
thenAcceptAsync
方法的作用与thenAccept
类似,但是允许你在异步任务完成时以异步的方式执行一个处理结果的操作。
具体来说,thenAcceptAsync
方法会将传入的消费者函数(Consumer)以异步的方式执行,而不会阻塞当前线程。这样可以更好地利用线程资源,特别是在处理大量异步任务时。
它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
例子:
thenRun / thenRunAsync
thenRun
方法的作用是在一个CompletableFuture
完成时执行一个操作,不关心前一个任务的结果,并且不返回任何结果。换句话说,它用于在异步任务完成后执行一个操作,而这个操作不需要处理结果。
具体来说,thenRun
方法接受一个参数,该参数是一个 Runnable 接口的实例,表示要执行的操作。这个操作会在前一个CompletableFuture
完成时被调用。
thenRun
方法适用于那些你只需要在异步任务完成后执行一些操作,而不关心任务结果的情况。这可以用于清理资源、记录日志等操作。
例子:
thenRunAsync
方法的作用与thenRun
类似,但是允许你以异步的方式执行一个操作,不会阻塞当前线程。
具体来说,thenRunAsync
方法接受一个参数,该参数是一个Runnable
接口的实例,表示要执行的操作。这个操作会在前一个CompletableFuture
完成时以异步的方式被调用,因此不会阻塞当前线程。
它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
例子:
多个任务获取最先完成
对首先完成的任务单独处理。
applyToEither / applyToEitherAsync
applyToEither()
用于在多个异步操作中,只要有一个操作完成就对其结果进行处理。它允许你将一个函数应用于第一个完成的操作的结果,并返回一个新的CompletableFuture
对象,表示处理后的结果。
具体来说,applyToEither()
方法的作用是在多个异步操作中,等待任意一个操作完成,然后将其结果应用于一个函数,得到一个新的计算结果。
例子:
applyToEitherAsync()
类似于applyToEither()
,但允许在指定的执行器中异步地执行操作。与applyToEither()
不同的是,applyToEitherAsync()
方法可以在多个异步操作中,等待任意一个操作完成后,将其结果应用于一个函数,并在指定的执行器中执行这个处理操作,然后返回一个新的CompletableFuture
对象,表示处理后的结果。
具体来说,applyToEitherAsync()
方法的作用是在多个异步操作中,等待任意一个操作完成,然后将其结果应用于一个函数,并在指定的执行器中执行这个处理操作,得到一个新的计算结果。
它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
例子:
acceptEither / acceptEitherAsync
acceptEither
方法用于指定一个回调函数(Consumer),该函数会在两个 CompletableFuture 中的任意一个完成时被调用。这个方法并不关心哪个 CompletableFuture 先完成,只要有一个完成,就会调用你提供的回调函数。回调函数接受完成的结果作为参数,但不返回任何结果。这个方法适用于你只关心结果的消费操作,例如日志记录或通知等。
acceptEitherAsync
与acceptEither
方法类似,但具有异步执行的特性。
具体来说,acceptEitherAsync
方法会在两个 CompletableFuture 中的任意一个完成时,将提供的回调函数提交给默认的ForkJoinPool
或指定的Executor
进行异步执行。这可以提供更好的并发性能,尤其在大规模的异步任务处理中。
它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
例子:
runAfterEither / runAfterEitherAsync
runAfterEither
用于在两个 CompletableFuture 中的任意一个完成时执行一个无返回值的动作(Runnable)。它允许你定义一个动作,该动作会在其中一个 CompletableFuture 完成时被触发,无论哪个 CompletableFuture 先完成。
具体来说,runAfterEither
方法会在其中一个 CompletableFuture 完成时,触发指定的动作(Runnable)。这个方法不关心任务完成的结果,也不返回结果。这在你需要在两个异步任务中的任意一个完成后执行某个操作时很有用。
例子:
runAfterEitherAsync
用于在两个 CompletableFuture 中的任意一个完成后,异步执行一个动作(Runnable)。这个方法类似于runAfterEither
,既不关心任务完成的结果,也不返回结果,但它会将指定的动作提交给默认的ForkJoinPool
或指定的Executor
进行异步执行。
具体来说,runAfterEitherAsync
方法会在其中一个 CompletableFuture 完成后,将指定的动作异步执行,无论哪一个 CompletableFuture 先完成。与runAfterEither
不同,runAfterEitherAsync
提供了异步执行的特性,可以更好地处理大规模的异步任务。
它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
例子:
处理异常
处理任务执行中发生的异常情况。
exceptionally
exceptionally()
用于处理异步操作中发生的异常情况。它允许你注册一个回调函数,当异步操作抛出异常时,该回调函数会被调用,并提供一个默认值或另一个计算结果作为处理。
具体来说,exceptionally()
方法的作用是为异步操作设置一个异常处理器,以便在操作出现异常时执行特定的处理逻辑,而不是让异常传播到上层调用。
exceptionally()
可以在链路中的结尾加,也能在链路中的中间加。
例子:
handle / handleAsync
handle
用于在异步任务完成后执行一个处理函数,这个函数可以处理任务的结果或异常。handle
方法允许你在任务完成后进行一些后续处理,无论任务是否成功完成。
具体来说,handle
方法接收一个BiFunction
参数,该参数会在任务完成后被调用。如果任务正常完成,处理函数会接收任务的结果作为第一个参数,而如果任务发生异常,处理函数会接收异常作为第一个参数,你可以在处理函数中进行相应的处理,然后返回一个新的结果或异常。
例子:
handleAsync
用于在异步任务完成后异步执行一个处理函数,类似于handle
方法,但handleAsync
具有异步执行的特性。
具体来说,handleAsync
方法接收一个BiFunction
参数,该参数会在任务完成后被调用。如果任务正常完成,处理函数会接收任务的结果作为第一个参数,而如果任务发生异常,处理函数会接收异常作为第一个参数。与handle
不同的是,handleAsync
方法会将处理函数提交给默认的ForkJoinPool
或指定的Executor
进行异步执行。
它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
例子:
whenComplete / whenCompleteAsync
whenComplete
用于在异步任务完成后执行一个动作(Consumer)。不同于handle
方法,whenComplete
方法无论任务成功完成还是发生异常,都会执行指定的动作。
具体来说,whenComplete
方法接收一个BiConsumer
参数,该参数会在任务完成后被调用。如果任务正常完成,处理函数会接收任务的结果作为第一个参数,而如果任务发生异常,处理函数会接收异常作为第一个参数。无论是哪种情况,你都可以在处理函数中进行相应的操作。
注意,当出现异常时whenComplete
虽然可以处理异常,但是出现异常时就也会抛出异常,需要你手动处理该异常,比如在whenComplete
后链式调用exceptionally
方法。
例子:
whenCompleteAsync
用于在异步任务完成后异步执行一个动作(Consumer)。类似于whenComplete
方法,但whenCompleteAsync
具有异步执行的特性。
具体来说,whenCompleteAsync
方法接收一个BiConsumer
参数,该参数会在任务完成后被调用。如果任务正常完成,处理函数会接收任务的结果作为第一个参数,而如果任务发生异常,处理函数会接收异常作为第一个参数。与whenComplete
不同的是,whenCompleteAsync
方法会将处理函数提交给默认的ForkJoinPool
或指定的Executor
进行异步执行。
它有如下两个方法:
-
使用默认线程池(ForkJoinPool.commonPool)的方法
-
指定自定义线程池的重载方法
例子:
批量执行任务
场景:需要批量执行多个任务,每次任务数量不固定,可能是 10 个,也可能是 20 个,需要等到所有任务都执行完毕才能进行下一步。比方说批量请求第三方接口查询价格,等所有价格都查询结束后再进行后续操作。
评论区