目录
CompletableFuture基本介绍
CompletableFuture 是 Java 8 引入的一个强大的异步编程工具类,用于支持异步编程和非阻塞操作。它提供了一种非阻塞的方式来执行任务,并允许你在任务完成时执行后续操作。相比于传统的 Future 类,CompletableFuture 提供了更多灵活且强大的功能,比如链式调用、任务合并、异常处理等,非常适合处理复杂的异步任务。
CompletableFuture继承结构
CompletableFuture属于 java.util.concurrent 包的一部分,实现了Future接口和CompletionStage接口,前者用于表示异步计算的结果,后者用于表示异步任务之间的依赖关系和组合操作。
CompletableFuture如果不提供线程池的话,默认使用的ForkJoinPool,依赖于 ForkJoinPool 来执行其异步计算,ForkJoinPool内部是守护线程,如果main线程结束了,守护线程会跟着一起结束。
主要功能
异步执行
可以在后台线程中异步执行任务,而不阻塞主线程。
supplyAsync 和 runAsync 是 CompletableFuture 类中的两个常用方法,用于在异步线程中执行任务。
supplyAsync(异步执行任务,任务有返回值)
执行有返回值的异步任务(基于 Supplier<U>),返回一个 CompletableFuture<U>。
示例1:基本使用
代码说明:
1.supplyAsync 方法接受一个 lambda 表达式,该表达式模拟了一个耗时的计算(2秒)。
2.一旦任务完成,结果(42)会被传递到 thenAccept 方法中进行处理。
3.主线程不会被阻塞,而是继续执行其他工作。
4.在示例中,主线程在执行完异步任务的处理后,调用 Thread.sleep(3000),确保主线程等待足够的时间,以便异步任务能够完成并打印结果。
5.输出将会包括 Result: 42,并且在等待后,主线程将结束。
执行结果:
确保在程序中添加了足够的等待时间,以避免主线程提前退出。
CompletableFuture如果不提供线程池的话,默认使用的ForkJoinPool,而ForkJoinPool内部是守护线程,如果main线程结束了,守护线程会跟着一起结束。
解释:
1."Doing other work...":主线程在执行 supplyAsync 方法时,会立即打印这行信息,因为异步任务是在另一个线程中执行的,不会阻塞主线程。
2."Result: 42":当异步任务完成(即经过约 2 秒后返回结果 42)时,thenAccept 方法被调用,打印出结果。
3."Main thread finished.":主线程结束。
执行流程:
- 主线程开始执行 supplyAsync 方法,启动一个新的异步任务。
- 主线程继续执行,打印出 "Doing other work..."。
- 异步任务在后台线程中运行,经过 2 秒后完成并返回结果 42。
- 一旦异步任务完成,thenAccept 方法接收到结果并打印 "Result: 42"。
- 主线程结束。
示例2:自定义线程池的使用
可以使用自定义线程池来执行异步任务。
使用自定义线程池来可以更好地管理资源,可以更好地控制线程的数量和行为,提高并发任务的管理和执行效率。
执行结果:
执行流程:
1.主线程启动。
2.主线程创建线程池。
3.提交异步任务。
在主线程中,使用 CompletableFuture.supplyAsync(...) 提交一个异步任务,异步任务则在自定义线程池中的工作线程中执行,与主线程并行运行。
4.主线程继续执行,输出“主线程继续执行...”的消息。
5.主线程调用 future.join(); 等待异步任务完成。
6.当异步任务完成后,主线程执行 executor.shutdown(); 关闭线程池。
7.最后,主线程输出“主线程结束”。
runAsync(异步执行任务,任务没有返回值)
执行无返回值的异步任务(基于 Runnable),返回 CompletableFuture<Void>,表示没有结果返回。
runAsync 方法属于 CompletableFuture 类的一个静态方法。它用于在异步线程中执行不返回结果的任务。
示例1:基本使用
执行结果:
示例2:自定义线程池的使用
执行结果:
总结
- 当你需要在异步任务中计算和返回一个值时,使用 supplyAsync。
- 当你只需要执行一些操作而不需要返回结果时,使用 runAsync。
主要区别:
等待结果
可以使用 join() 或 get() 方法等待计算结果,join() 方法不会抛出检查异常,而 get() 方法会抛出。
join
等待 CompletableFuture 完成,返回结果,若有异常则抛出。
join() 方法是 CompletableFuture 类中的一个重要方法,用于阻塞当前线程,直到异步任务完成并返回结果。它会以 CompletionException 的形式处理异常。
示例1:基本使用
代码说明:
1.示例中,supplyAsync 方法启动了一个异步任务,该任务在后台线程中执行,模拟了一个 2 秒的延迟。
2.使用 future.join() 方法,主线程会阻塞,直到异步任务完成并返回结果。
3.当异步任务完成后,结果(42)将被打印。
4.主线程结束。
执行结果:
示例 2:异常处理
如果异步任务抛出异常,使用join()会抛出 CompletionException,
不会抛出 InterruptedException,可以通过 getCause() 方法获取原始异常。
使用 join() 可以方便地处理异步结果,并在需要时进行异常处理。
代码说明:
1.在这个示例中,异步任务抛出一个 RuntimeException。
2.使用 join() 方法时,如果任务失败,CompletionException 将被抛出。
3.可以通过 e.getCause() 获取原始异常并打印错误信息。
执行结果:
get
等待 CompletableFuture 完成并返回结果,但会抛出检查异常。
get() 方法会抛出 InterruptedException 和 ExecutionException。
InterruptedException 表示当前线程在等待结果时被中断。
ExecutionException 包含计算过程中抛出的异常,可以通过 getCause() 方法获取原始异常。
示例1:
执行结果:
总结
- join() 方法和 get() 方法都是 CompletableFuture 中用于获取异步计算结果的方法。
- 两者都可以阻塞当前线程,直到异步计算完成并返回结果。
- 使用 get() 时需要处理 InterruptedException 和 ExecutionException,使得异常处理更加复杂。适用于需要对中断进行处理的场景,或者你需要捕获 InterruptedException 的情况。
- 使用 join() 时只需要处理 CompletionException,使得代码更简洁。适用于更简单的异步处理场景,特别是在不需要管理中断的情况下。
- 如果不需要关注中断,推荐使用 join() 方法。join() 更加简洁。
链式调用
支持方法链,可以通过 thenAccept、 thenApply等方法添加后续操作。
thenAccept(消费结果,无返回值)
返回一个CompletableFuture<Void>,表示没有返回值。
在 CompletableFuture 完成时执行一个操作,但不返回结果。
thenAccept()处理返回结果,不返回新的 CompletableFuture。
示例1:
说明:
1.thenAccept 接收一个 Consumer,在异步任务完成后处理结果,但不返回值。
执行结果:
thenApply
在 CompletableFuture 完成时执行一个函数,处理其结果,返回一个新的 CompletableFuture。
对上一个任务的结果进行同步转换,返回新值。
thenApply()处理返回结果并返回新的 CompletableFuture。
示例1:
说明:
1.thenApply 接收一个 Function,在完成时对结果进行转换操作,并返回新的结果。
执行结果:
示例 2:
使用 CompletableFuture 进行任务链式调用,同时结合 thenApply 和 thenAccept 方法。
链式调用 CompletableFuture 来处理异步计算的结果。
thenApply 用于对结果进行转换,而 thenAccept 用于处理最终结果。
使用 join() 可以在需要时阻塞主线程,直到异步任务完成。
执行流程:
1.启动异步任务:
supplyAsync 启动一个异步任务,模拟一个 2 秒的长时间运行任务,返回结果 10。
2.第一个 thenApply:
在第一个 thenApply 中,接收结果 10,再模拟一个 1 秒的长时间运行任务,将结果翻倍,返回 20。
3.第二个 thenApply:
在第二个 thenApply 中,将上一个结果 20 增加 1,返回 21。
4.输出最终结果:
使用 thenAccept 输出最终结果 21。这一步不会阻塞主线程。
5.主线程继续执行:
主线程打印 "Doing other work...",并继续执行。
6.使用 join():
主线程调用 future.join(),这会阻塞主线程,直到异步任务完成并返回结果 21。
7.打印结果:
最后,主线程打印结果和结束信息。
总结
- thenAccept:用于处理异步计算的结果,但不返回任何结果,适用于只关心结果的场景。
- thenApply:用于对结果进行转换并返回一个新的结果,适用于需要对结果进行进一步处理的场景。
任务组合(组合多个 CompletableFuture)
可以通过 thenCombine、thenCompose 等方法将多个CompletableFuture 结合在一起。
thenCombine 和 thenCompose 是 CompletableFuture 类中用于组合多个异步计算结果的方法。它们在处理多个 CompletableFuture 时非常有用。
thenCombine(合并两个独立任务的结果)
结合两个CompletableFuture 的结果,在它们都完成后执行操作。
thenCombine 用于将两个独立的 CompletableFuture 结果组合在一起。
当两个 CompletableFuture 都完成时,它会执行一个合并操作,组合它们的结果,并返回一个新的 CompletableFuture。
示例1:
说明:
1.创建两个异步任务:
future1 和 future2 分别返回 10 和 20,各自模拟了不同的延迟时间。
2.使用 thenCombine:
当 future1 和 future2 都完成后,thenCombine 会被调用,合并两个结果并返回 30。
3.打印结果:
使用 thenAccept 打印合并后的结果。
4.等待完成:
combinedFuture.join() 阻止主线程继续执行,直到合并的 CompletableFuture 完成。
执行结果:
thenCompose
将一个 CompletableFuture 的结果作为另一个 CompletableFuture 的输入,用于链式调用。返回另一个 CompletableFuture。
thenCompose 用于将一个 CompletableFuture 的结果转化为另一个 CompletableFuture。它常用于需要链式调用,且后一个操作依赖于前一个操作的结果的情况。
示例1:
说明:
1.创建第一个异步任务:
使用 supplyAsync 创建一个异步任务,返回 10,并模拟延迟。
2.使用 thenCompose:
当第一个任务完成时,thenCompose 会被调用。
它返回另一个 CompletableFuture,该任务将前一个结果(10)乘以 2,并模拟延迟。
3.打印最终结果:
使用 thenAccept 打印最终结果 20。
4.等待完成:
composedFuture.join() 确保主线程等待这个组合的 CompletableFuture 完成,防止程序过早退出。
执行结果:
总结
- thenCombine:
用于合并两个独立的 CompletableFuture 的结果。当两个 CompletableFuture 都完成时,执行合并操作。
- thenCompose:
将一个 CompletableFuture 的结果映射到另一个 CompletableFuture,前一个任务的结果用于下一个任务。
异常处理
在CompletableFuture中,可以用exceptionally 和 handle 方法处理异常情况。
exceptionally
为 CompletableFuture 定义异常处理逻辑。
exceptionally 方法用于处理 CompletableFuture 中发生的异常。如果在计算过程中发生了异常,该方法会被调用,并允许你提供一个替代的结果。
示例1:
执行结果:
handle
在计算完成后处理结果和异常,可以返回新的结果。
handle 方法也可以用于处理异常,它不仅可以处理异常,还可以处理正常结果。它接受两个参数,一个是正常结果,另一个是异常信息(如果有的话)。它的返回值可以是一个新的结果,无论是正常的还是替代的。
示例1:
执行结果:
总结
- exceptionally: 主要用于处理异常,返回一个替代值。
- handle: 可以处理正常结果和异常,返回一个新的结果。