概述
CompletableFuture
是对Future
的扩展和增强,CompletableFuture
实现了Future接口,并在此基础上进行了丰富的扩展,弥补了Future
的局限性,同时CompletableFuture
实现了对任务编排的能力。这样可以轻松的组织不同任务的运行顺序、规则以及方式。从某种程度上来说,这项能力是它的核心能力。
虽然在以前通过CountDownLatch
等工具类也可以实现任务的编排,但需要复杂的逻辑处理,不仅耗费精力并且很难维护
CompletableFuture
的继承结构如下:

CompletionStage
接口定了任务编排的方法,执行某一阶段,可以向下执行后续阶段。异步执行的,默认线程池是ForkJoinPool.commonPool()
,但为了业务之间互不影响,且便于定位问题,强烈推荐使用自定义线程池。
CompletableFuture
中默认线程池如下
java
// 根据commonPool的并行度来选择,而并行度的计算是在ForkJoinPool的静态代码段完成的
private static final boolean useCommonPool =
(ForkJoinPool.getCommonPoolParallelism() > 1);
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
创建异步任务
1、supplyAsync
supplyAsync
是创建带有返回值的异步任务,它有如下两个方法,
- 一个是使用默认线程池 (
ForkJoinPool.commonPool()
)的方法, - 一个是带有自定义线程池的重载方法
swift
// 带返回值异步请求,默认线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
// 带返回值的异步请求,可以自定义线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
创建异步任务测试:
java
package CompletableFuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo11 {
public static void main(String[] args) {
//创建异步任务
//使用默认的线程池
CompletableFuture<String> comFu1=CompletableFuture.supplyAsync(()->{
System.out.println("do......Something");
return "result";
});
//等待任务执行完成
try {
System.out.println("任务的结果为"+comFu1.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
//使用自定义的线程池
ExecutorService executorService= Executors.newSingleThreadExecutor();
CompletableFuture<String> comFu2=CompletableFuture.supplyAsync(()->{
System.out.println("do......Something");
return "result";
},executorService);
//等待子任务执行完成
try {
System.out.println("任务的结果为"+comFu2.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}
运行结果:
do......Something 任务的结果为result do......Something 任务的结果为result
2、runAsync
runAsync
是创建没有返回值的异步任务。它有如下两个方法:
- 一个是使用默认线程池 (
ForkJoinPool.commonPool()
)的方法 - 一个是带有自定义线程池的重载方法
java
// 不带返回值的异步请求,默认线程池
public static CompletableFuture<Void> runAsync(Runnable runnable)
// 不带返回值的异步请求,可以自定义线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
创建异步任务测试:
csharp
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
System.out.println("do something....");
});
//等待任务执行完成
System.out.println("结果->" + cf.get());
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 自定义线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
System.out.println("do something....");
}, executorService);
//等待任务执行完成
System.out.println("结果->" + cf.get());
}
这创建异步任务的四个方法的区别:
runAsync()
以Runnable函数式接口类型为参数 ,没有返回结果 ,supplyAsync()
以Supplier函数式接口类型为参数,返回结果类型为U;Supplier
接口的 get()是有返回值的(会阻塞)- 使用没有指定
Executor
的方法时,内部使用ForkJoinPool.commonPool()
作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。 - 默认情况下
CompletableFuture
会使用公共的ForkJoinPool
线程池,这个线程池默认创建的线程数是 CPU 的核数(也可以通过 JVMoption:-Djava.util.concurrent.ForkJoinPool.common.parallelism
来设置ForkJoinPool
线程池的线程数)。如果所有CompletableFuture共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的线程池,以避免互相干扰
3、获取任务结果的方法
java
// 如果完成则返回结果,否则就抛出具体的异常
public T get() throws InterruptedException, ExecutionException
// 最大时间等待返回结果,否则就抛出具体异常
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
// 完成时返回结果值,否则抛出unchecked异常。为了更好地符合通用函数形式的使用,如果完成此 CompletableFuture所涉及的计算引发异常,则此方法将引发unchecked异常并将底层异常作为其原因
public T join()
// 如果完成则返回结果值(或抛出任何遇到的异常),否则返回给定的 valueIfAbsent。
public T getNow(T valueIfAbsent)
// 如果任务没有完成,返回的值设置为给定值
public boolean complete(T value)
// 如果任务没有完成,就抛出给定异常
public boolean completeExceptionally(Throwable ex)
基本使用也很简单,根据编写代码的实际情况进行选择到底该用那种方式来获取任务的结果
获取结果(join&get)
的区别 join()和get()
方法都是用来获取CompletableFuture
异步之后的返回值。
join()
方法抛出的是uncheck
异常(即未经检查的异常),不会强制开发者抛出。
get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException
需要用户手动处理(抛出或者 try catch)
异步回调处理
1、thenApply和thenApplyAsync
thenApply
表示某个任务执行完成后执行的动作,即回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中,带有返回值。
基本使用:
kotlin
package CompletableFuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class Demo22 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> comfu1=CompletableFuture.supplyAsync(()->{
System.out.println("comfu1 do something:"+Thread.currentThread().getName());
return 1;
});
CompletableFuture<Integer> comfu2 = comfu1.thenApply((result) ->{
System.out.println("comfu2 do something:"+Thread.currentThread().getName());
result=result+2;
return result;
});
//等待任务1执行完成
System.out.println("comfu1结果->" + comfu1.get());
//等待任务2执行完成
System.out.println("comfu2结果->" + comfu2.get());
CompletableFuture<Integer> comfu3=CompletableFuture.supplyAsync(()->{
System.out.println("comfu3 do something:"+Thread.currentThread().getName());
return 1;
});
CompletableFuture<Integer> comfu4 = comfu1.thenApplyAsync((result) ->{
System.out.println("comfu4 do something:"+Thread.currentThread().getName());
result=result+2;
return result;
});
//等待任务1执行完成
System.out.println("comfu3结果->" + comfu3.get());
//等待任务2执行完成
System.out.println("comfu4结果->" + comfu4.get());
}
}
运行结果:
comfu1 do something:ForkJoinPool.commonPool-worker-9 comfu2 do something:main comfu1结果->1 comfu2结果->3 comfu3 do something:ForkJoinPool.commonPool-worker-9 comfu3结果->1 comfu4 do something:ForkJoinPool.commonPool-worker-9 comfu4结果->3
区别:
thenApply
表示某个任务执行完成后执行的动作,即回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中,有返回值。- thenApplyAsync与thenApply的区别在于, 前者是将job2提交到线程池中异步执行,实际执行job2的线程可能是另外一个线程 ,后者是由执行job1的线程立即执行job2,即两个job都是同一个线程执行的
- 我发现如果第一个任务如果执行时间比较长(比如sleep个几毫秒或者有大的for循环等),那么第二个任务就会由上一个线程执行,否则由main线程执行
thenApply
实际上不一定是上一个线程执行,还有可能是主线程执行
并且thenApplyAsyn
c可以自定义线程池,默认的使用ForkJoinPool.commonPool()
线程池。
2、thenAccept和thenAcceptAsync
thenAccept
表示某个任务执行完成后执行的动作,即回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中 ,无返回值。
基本使用:
kotlin
package CompletableFuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class Demo33 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> comfu1=CompletableFuture.supplyAsync(()->{
System.out.println("comfu1 do something:"+Thread.currentThread().getName());
return 1;
});
CompletableFuture<Void> comfu2 = comfu1.thenAccept((result) -> {
System.out.println("comfu2 do something:" + Thread.currentThread().getName());
});
//等待任务1执行完成
System.out.println("comfu1结果->" + comfu1.get());
//等待任务2执行完成
System.out.println("comfu2结果->" + comfu2.get());
CompletableFuture<Integer> comfu3=CompletableFuture.supplyAsync(()->{
System.out.println("comfu3 do something:"+Thread.currentThread().getName());
return 1;
});
CompletableFuture<Void> comfu4 = comfu1.thenAcceptAsync((result) ->{
System.out.println("comfu4 do something:"+Thread.currentThread().getName());
});
//等待任务1执行完成
System.out.println("comfu3结果->" + comfu3.get());
//等待任务2执行完成
System.out.println("comfu4结果->" + comfu4.get());
}
}
运行结果: comfu1 do something:ForkJoinPool.commonPool-worker-9 comfu2 do something:main comfu1结果->1 comfu2结果->null comfu3 do something:ForkJoinPool.commonPool-worker-9 comfu3结果->1 comfu4 do something:ForkJoinPool.commonPool-worker-9 comfu4结果->null
thenAccep和thenAccepAsync
区别在于:
使用thenAccep
方法时子任务与父任务使用的是同一个线程,不一定是上一个线程执行,还有可能是主线程执行 ,而thenAccepAsync
在子任务中可能是另起一个线程执行任务 ,并且thenAccepAsync
可以自定义线程池,默认的使用ForkJoinPool.commonPool()
线程池。
3、thenRun和thenRunAsync
thenRun表示任务执行完后执行的动作,即回调方法,无入参,无返回值
基本使用:
kotlin
package CompletableFuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class Demo44 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> comfu1=CompletableFuture.supplyAsync(()->{
System.out.println("comfu1 do something:"+Thread.currentThread().getName());
return 1;
});
CompletableFuture<Void> comfu2 = comfu1.thenRun(() -> {
System.out.println("comfu2 do something:" + Thread.currentThread().getName());
});
//等待任务1执行完成
System.out.println("comfu1结果->" + comfu1.get());
//等待任务2执行完成
System.out.println("comfu2结果->" + comfu2.get());
CompletableFuture<Integer> comfu3=CompletableFuture.supplyAsync(()->{
System.out.println("comfu3 do something:"+Thread.currentThread().getName());
return 1;
});
CompletableFuture<Void> comfu4 = comfu1.thenRunAsync(() ->{
System.out.println("comfu4 do something:"+Thread.currentThread().getName());
});
//等待任务1执行完成
System.out.println("comfu3结果->" + comfu3.get());
//等待任务2执行完成
System.out.println("comfu4结果->" + comfu4.get());
}
}
运行结果
comfu1 do something:ForkJoinPool.commonPool-worker-9 comfu2 do something:main comfu1结果->1 comfu2结果->null comfu3 do something:ForkJoinPool.commonPool-worker-9 comfu3结果->1 comfu4 do something:ForkJoinPool.commonPool-worker-9 comfu4结果->null
thenRun和thenRunAsync区别在于:
使用thenRun
方法时子任务与父任务使用的是同一个线程,不一定是上一个线程执行,还有可能是主线程执行 ,而thenRunAsync
在子任务中可能是另起一个线程执行任务,并且thenRunAsync
可以自定义线程池,默认的使用ForkJoinPool.commonPool()
线程池。
3、whenComplete和whenCompleteAsync
whenComplete
是当某个任务执行完成后执行的回调方法,会将执行结果或者执行期间抛出的异常传递给回调方法,如果是正常执行则异常为null,回调方法对应的CompletableFuture
的result和该任务一致,如果该任务正常执行,则get 方法返回执行结果,如果是执行异常,那么get方法抛出异常
kotlin
package CompletableFuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class Demo55 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> comfu1=CompletableFuture.supplyAsync(()->{
System.out.println("comfu1 do something:"+Thread.currentThread().getName());
// int a=1/0;
return 1;
});
CompletableFuture<Integer> comfu2 = comfu1.whenComplete((result, e) -> {
System.out.println("上个任务的结果"+result);
System.out.println("上个任务抛出异常"+e);
System.out.println(Thread.currentThread()+"comfu2 do something");
});
//等待任务1执行完成
System.out.println("comfu1结果->" + comfu1.get());
//等待任务2执行完成
System.out.println("comfu2结果->" + comfu2.get());
}
}
运行结果:
comfu1 do something:ForkJoinPool.commonPool-worker-9 上个任务的结果1 上个任务抛出异常null Thread[main,5,main]comfu2 do something comfu1结果->1 comfu2结果->1
如果解开int a =1/0; 那么就会抛出异常:
上个任务的结果null 上个任务抛出异常java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero Thread[main,5,main]comfu2 do something Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357) at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895) at CompletableFuture.Demo55.main(Demo55.java:20) Caused by: java.lang.ArithmeticException: / by zero at CompletableFuture.Demo55.lambda <math xmlns="http://www.w3.org/1998/Math/MathML"> m a i n main </math>main0(Demo55.java:10) at java.util.concurrent.CompletableFuture <math xmlns="http://www.w3.org/1998/Math/MathML"> A s y n c S u p p l y . r u n ( C o m p l e t a b l e F u t u r e . j a v a : 1590 ) a t j a v a . u t i l . c o n c u r r e n t . C o m p l e t a b l e F u t u r e AsyncSupply.run(CompletableFuture.java:1590) at java.util.concurrent.CompletableFuture </math>AsyncSupply.run(CompletableFuture.java:1590)atjava.util.concurrent.CompletableFutureAsyncSupply.exec(CompletableFuture.java:1582) at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
whenCompleteAsync和whenComplete
区别也是whenCompleteAsync
可能会另起一个线程执行任务,并且thenRunAsync可以自定义线程池,默认的使用ForkJoinPool.commonPool()线程池。
4、handle和handleAsync
跟whenComplete
基本一致,区别在于handle的回调方法有返回值。
csharp
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + " cf1 do something....");
// int a = 1/0;
return 1;
});
CompletableFuture<Integer> cf2 = cf1.handle((result, e) -> {
System.out.println(Thread.currentThread() + " cf2 do something....");
System.out.println("上个任务结果:" + result);
System.out.println("上个任务抛出异常:" + e);
return result+2;
});
//等待任务2执行完成
System.out.println("cf2结果->" + cf2.get());
}
多任务组合处理
依赖关系
thenApply()
:把前面任务的执行结果,交给后面的FunctionthenCompose():
用来连接两个有依赖关系的任务,结果由第二个任务返回
and集合关系
thenCombine()
:合并任务,有返回值thenAccepetBoth():
两个任务执行完成后,将结果交给thenAccepetBoth
处理,无返回值runAfterBoth()
:两个任务都执行完成后,执行下一步操作(Runnable类型任务)
or聚合关系*
applyToEither():
两个任务哪个执行的快,就使用哪一个结果,有返回值acceptEither()
:两个任务哪个执行的快,就消费哪一个结果,无返回值runAfterEither():
任意一个任务执行完成,进行下一步操作(Runnable类型任务)
并行执行
allOf()
:当所有给定的CompletableFuture
完成时,返回一个新的CompletableFuture
anyOf():
当任何一个给定的CompletablFuture
完成时,返回一个新的CompletableFuture
结果处理
whenComplete
:当任务完成时,将使用结果(或 null)和此阶段的异常(或 null如果没有)执行给定操作exceptionally:
返回一个新的CompletableFuture,当前面的CompletableFuture完成时,它也完成,当它异常完成时,给定函数的异常触发这个CompletableFuture的完成
1、thenCombine、thenAcceptBoth 和runAfterBoth
这三个方法都是将两个CompletableFuture
组合起来处理,只有两个任务都正常完成时,才进行下阶段任务。
区别:
thenCombine
会将两个任务的执行结果作为所提供函数的参数,且该方法有返回值;
thenAcceptBoth
同样将两个任务的执行结果作为方法入参,但是无返回值;
runAfterBoth
没有入参,也没有返回值。
注意两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果。
ini
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + " cf1 do something....");
return 1;
});
CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + " cf2 do something....");
return 2;
});
CompletableFuture<Integer> cf3 = cf1.thenCombine(cf2, (a, b) -> {
System.out.println(Thread.currentThread() + " cf3 do something....");
return a + b;
});
System.out.println("cf3结果->" + cf3.get());
}
2、applyToEither、acceptEither和runAfterEither
这三个方法和上面一样也是将两个CompletableFuture组合起来处理,当有一个任务正常完成时,就会进行下阶段任务。
区别:
applyToEither
会将已经完成任务的执行结果作为所提供函数的参数,且该方法有返回值;
acceptEither
同样将已经完成任务的执行结果作为方法入参,但是无返回值;
runAfterEither
没有入参,也没有返回值。
基本使用:
csharp
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
try {
System.out.println(Thread.currentThread() + " cf1 do something....");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "cf1 任务完成";
});
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
try {
System.out.println(Thread.currentThread() + " cf2 do something....");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "cf2 任务完成";
});
CompletableFuture<String> cf3 = cf1.applyToEither(cf2, (result) -> {
System.out.println("接收到" + result);
System.out.println(Thread.currentThread() + " cf3 do something....");
return "cf3 任务完成";
});
System.out.println("cf3结果->" + cf3.get());
}
3、allOf / anyOf
allOf:
CompletableFuture是多个任务都执行完成后才会执行,只有有一个任务执行异常,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回null。
anyOf :
CompletableFuture是多个任务只要有一个任务执行完成 ,则返回的CompletableFuture
,执行get方法时会抛出异常,如果都是正常执行,则get返回执行完成任务的结果。
csharp
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
try {
System.out.println(Thread.currentThread() + " cf1 do something....");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("cf1 任务完成");
return "cf1 任务完成";
});
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
try {
System.out.println(Thread.currentThread() + " cf2 do something....");
int a = 1/0;
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("cf2 任务完成");
return "cf2 任务完成";
});
CompletableFuture<String> cf3 = CompletableFuture.supplyAsync(() -> {
try {
System.out.println(Thread.currentThread() + " cf2 do something....");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("cf3 任务完成");
return "cf3 任务完成";
});
CompletableFuture<Void> cfAll = CompletableFuture.allOf(cf1, cf2, cf3);
System.out.println("cfAll结果->" + cfAll.get());
}