CompletableFuture实现了CompletionStage和Future两个接口,增加了异步回调、流式处理、多个Futrue组合处理的能力。
1.创建异步任务
1)submit
通常的线程池接口类ExecutorService中的execute方法返回值是void,即无法获取异步任务的执行状态;submit方法的返回值是Future,可以从此对象中获取任务执行的状态和结果,示例如下:
public void submitTest() throws ExecutionException, InterruptedException {
//创建异步执行任务
ExecutorService executorService = Executors.newSingleThreadExecutor();
//声明Future,submit方法的参数为一个回调函数
Future<String> cf = executorService.submit(() -> {
System.out.println(Thread.currentThread() + "start,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit,time:" + System.currentTimeMillis());
return "test";
});
System.out.println("main thread start,time:" + System.currentTimeMillis());
//等待子任务执行完成,如果已完成则直接返回结果
//如果执行任务异常,则get方法会把之前捕获的异常抛出
System.out.println("result:" + cf.get());
System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
可以看出子线程是异步执行的,主线程休眠等待子线程执行完成,子线程执行完成后唤醒主线程,主线程获取结果后退出。
2)supplyAsync / runAsync
supplyAsync表示创建带返回值的异步任务,相当于ExecutorService.submit(Callable<T> task)方法;runAsync表示创建无返回值的异步任务,相当于ExecutorService.submit(Runnable task)方法。这两个方法的效果跟submit是一样的,测试用例如下:
public void supplyAsync() throws ExecutionException, InterruptedException {
//创建异步执行任务
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit,time:" + System.currentTimeMillis());
return "test";
});
System.out.println("main thread start,time:" + System.currentTimeMillis());
//等待子任务执行完成,如果已完成则直接返回结果
//如果执行任务异常,则get方法会把之前捕获的异常抛出
System.out.println("result:" + cf.get());
System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
public void supplyRun() throws ExecutionException, InterruptedException {
CompletableFuture cf = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread() + "start,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit,time:" + System.currentTimeMillis());
});
System.out.println("main thread start,time:" + System.currentTimeMillis());
//等待子任务执行完成,如果已完成则直接返回结果
//如果执行任务异常,则get方法会把之前捕获的异常抛出
System.out.println("result:" + cf.get());
System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
这两个方法各有一个重载版本,可以指定执行异步任务的Executor实现,如果不指定,默认使用ForkJoinPool.commonPool(),如果机器是单核的,则默认使用ThreadPerTaskExecutor。
public void supplyAsync() throws ExecutionException, InterruptedException {
ForkJoinPool pool = new ForkJoinPool();
//创建异步执行任务
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit,time:" + System.currentTimeMillis());
return "test";
},pool);
System.out.println("main thread start,time:" + System.currentTimeMillis());
//等待子任务执行完成,如果已完成则直接返回结果
//如果执行任务异常,则get方法会把之前捕获的异常抛出
System.out.println("result:" + cf.get());
System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
public void supplyRun() throws ExecutionException, InterruptedException {
ExecutorService executorService= Executors.newSingleThreadExecutor();
CompletableFuture cf = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread() + "start,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit,time:" + System.currentTimeMillis());
},executorService);
System.out.println("main thread start,time:" + System.currentTimeMillis());
//等待子任务执行完成,如果已完成则直接返回结果
//如果执行任务异常,则get方法会把之前捕获的异常抛出
System.out.println("result:" + cf.get());
System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
2.异步回调
1)thenApply / thenApplyAsync
thenApply表示某个任务执行完成后的回调方法,会将该任务的返回值作为参数传入回调方法中,测试用例如下:
public void thenApply() throws ExecutionException, InterruptedException {
ForkJoinPool pool = new ForkJoinPool();
//创建异步执行任务
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
return "test";
},pool);
//cf2使用cf的返回值作为参数,调用thenApply的回调函数
//thenApply这里实际创建了一个新的CompletableFuture实例
CompletableFuture<String> cf2 = cf.thenApply((result) -> {
System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
return "job2:test";
});
System.out.println("main thread start cf.get(),time:" + System.currentTimeMillis());
//等待子任务执行完成,如果已完成则直接返回结果
//如果执行任务异常,则get方法会把之前捕获的异常抛出
System.out.println("result:" + cf.get());
System.out.println("main thread start cf2.get(),time:" + System.currentTimeMillis());
System.out.println("result2:" + cf2.get());
System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
从返回结果可以看出,job1执行结束后,执行job2的时候是将job1的返回结果传入了job2。thenApplyAsync和thenApply的区别在于,前者是将job2提交到线程池中执行,实际执行job2的可能是另一个线程;后者是由同一个线程执行完job1再执行job2,将上述测试用例中的thenApply改成thenApplyAsync后,执行结果如下:
从返回结果可以看出,此时执行job1和job2的不再是同一个线程。thenApplyAsync有一个重载版本,可以指定异步任务执行的Executor实现,如果不指定。默认使用ForkJoinPool.commonPool()。
除了thenApply之外,还有thenAccept / thenRun两个方法,thenAccept 是将上一个任务的返回值作为参数,但是没有返回值;thenRun既没有入参,也没有返回值。这两个方法方法同理,每个方法都有一个Async结尾的方法,一个使用默认的Executor实现,另一个指定Executor实现;不带Async的方法由同一个线程执行job1和job2,带Async的会将任务提交到线程池,执行任务的线程与触发任务的线程不一定是同一个线程。
2)exceptionally
exceptionally方法是指定某个任务执行异常时的回调方法,会将抛出的异常作为参数传递到exceptionally的回调方法中,如果该任务正常执行会将exceptionally返回的CompletionStage的result作为该任务正常执行的结果,测试用例如下:
public void exceptionally() throws ExecutionException, InterruptedException {
ForkJoinPool pool = new ForkJoinPool();
//创建异步执行任务
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(true){
throw new RuntimeException("test");
}else{
System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
return "test";
}
},pool);
//cf2使用cf的返回值作为参数,调用thenApply的回调函数
//cf抛出异常时则不会调用cf2的回调
CompletableFuture<String> cf2 = cf.thenApplyAsync((result) -> {
System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
return "job2:test";
});
//cf执行异常时。会将抛出的异常作为参数传递给exceptionally的回调方法
CompletableFuture<String> cf3 = cf.exceptionally((exception) -> {
System.out.println(Thread.currentThread() + "start exception,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit exception,time:" + System.currentTimeMillis());
return "exception:test";
});
System.out.println("main thread start cf.get(),time:" + System.currentTimeMillis());
//调用cf2会先调用cf,cf抛出异常后会进入cf3的回调
System.out.println("result:" + cf2.get());
System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
3)whenComplete
whenComplete方法是当某个任务执行完成后的回调方法,会将执行结果或者抛出的异常作为参数传递给回调方法,如果是正常执行则异常为null,回调方法对应的CompletableFuture的result和该任务一致,如果该任务正常执行,则get方法返回执行结果,如果执行异常,则get方法抛出异常,测试用例如下:
public void whenComplete() throws ExecutionException, InterruptedException {
ForkJoinPool pool = new ForkJoinPool();
//创建异步执行任务
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(false){
throw new RuntimeException("test");
}else{
System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
return "test";
}
},pool);
//cf执行完会将执行结果和异常作为参数传入whenComplete的回调方法
CompletableFuture<String> cf2 = cf.whenComplete((res,exception) -> {
System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(Objects.nonNull(exception)){
exception.printStackTrace();
}else{
System.out.println("success,result:" + res);
}
System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
});
System.out.println("main thread start cf.get(),time:" + System.currentTimeMillis());
//如果cf是正常执行的,cf2.get的结果就是cf执行的结果
//如果cf是执行异常,则cf2.get会抛出异常
System.out.println("result:" + cf2.get());
System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
4)handle
handle和whenComplete基本一致,区别在于handle的回调方法有返回值,且handle方法返回的CompletableFuture的result是回调方法的执行结果或者回调方法执行期间抛出的异常,与原始的CompletableFuture无关,测试用例如下:
public void handle() throws ExecutionException, InterruptedException {
ForkJoinPool pool = new ForkJoinPool();
//创建异步执行任务
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(false){
throw new RuntimeException("test");
}else{
System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
return "test";
}
},pool);
//cf执行完会将执行结果和异常作为参数传入whenComplete的回调方法
CompletableFuture<String> cf2 = cf.handle((res,exception) -> {
System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
if(Objects.nonNull(exception)){
return exception.getMessage();
}else{
return "success,result:" + res;
}
});
System.out.println("main thread start cf.get(),time:" + System.currentTimeMillis());
//如果cf是正常执行的,cf2.get的结果是cf2执行的结果,与cf无关
System.out.println("result:" + cf2.get());
System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
3.组合处理
1)thenCombine / thenAcceptBoth / runAfterBoth
这三个方法都是将两个CompletableFuture组合起来,只有这两个都正常执行完了才会执行某个任务。区别在于,thenCombine会将两个任务的返回结果都作为参数传入回调方法,且该方法有返回值;thenAcceptBoth同样将两个任务的返回结果作为参数传入回调方法,但是没有返回值;runAfterBoth既没有入参也没有返回值。需要注意的是,两个任务中只要有一个执行异常,则会将该异常信息作为指定任务的执行结果,测试用例如下:
public void thenCombine() throws ExecutionException, InterruptedException {
//创建异步执行任务1
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
return "1";
});
//创建异步执行任务2
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
return "2";
});
//cf和cf2都执行完后,将执行结果作为参数传入cf3的回调方法
CompletableFuture<String> cf3 = cf.thenCombine(cf2,(a,b) -> {
System.out.println(Thread.currentThread() + "start job3,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job3,time:" + System.currentTimeMillis());
return a + b + "3";
});
System.out.println("main thread start,time:" + System.currentTimeMillis());
//等待子任务执行完成,如果已完成则直接返回结果
//如果执行任务异常,则get方法会把之前捕获的异常抛出
System.out.println("result:" + cf3.get());
System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
2)applyToEither / acceptEither / runAfterEither
这三个方法也是将两个CompletableFuture组合起来,只要其中任意一个执行完成,就会执行某个任务,只会将执行完的任务的返回结果作为参数传入回调函数。区别与apply / accept / run 的区别相同,测试用例如下:
public void applyToEither() throws ExecutionException, InterruptedException {
//创建异步执行任务1
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
return "1";
});
//创建异步执行任务2
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
try {
//cf2睡久一点方便看出执行顺序
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
return "2";
});
//cf或cf2任意一个执行完后,只将该执行完的任务的返回结果作为参数传入回调函数
CompletableFuture<String> cf3 = cf.applyToEither(cf2,(res) -> {
System.out.println(Thread.currentThread() + "start job3,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job3,time:" + System.currentTimeMillis());
return res + "3";
});
System.out.println("main thread start,time:" + System.currentTimeMillis());
//等待子任务执行完成,如果已完成则直接返回结果
//如果执行任务异常,则get方法会把之前捕获的异常抛出
System.out.println("result:" + cf3.get());
System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
3)thenCompose
thenCompose方法会在执行完某个任务后,将该任务的执行结果作为参数传入指定的方法,该方法会返回一个新的CompletableFuture实例,测试用例如下:
public void thenCompose() throws ExecutionException, InterruptedException {
//创建异步执行任务
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
return "1";
});
CompletableFuture<String> cf2 = cf.thenCompose((res) -> {
System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
return CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start job3,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job3,time:" + System.currentTimeMillis());
return "job3:test";
});
});
System.out.println("main thread start,time:" + System.currentTimeMillis());
System.out.println("result:" + cf2.get());
System.out.println("main thread exit,time:" + System.currentTimeMillis());
}
4)allOf / anyOf
allOf返回的CompletableFuture是多个任务执行完之后才会运行,只要有一个任务执行异常,回调函数就会抛出异常,如果所有任务都正常执行,get方法返回结果为null;anyOf返回的CompletableFuture是只要一个任务执行完成后就会运行,get方法返回的是已经执行完成的任务的返回结果。这两个方法的参数为不定数组,可以传入任意数量的CompletableFuture实例。测试用例如下:
public void allOf() throws ExecutionException, InterruptedException {
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start job1,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job1,time:" + System.currentTimeMillis());
return "1";
});
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + "start job2,time:" + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + "exit job2,time:" + System.currentTimeMillis());
return "2";
});
//allof等待所有任务执行完成才执行,如果有一个任务异常终止,则cf3.get时会抛出异常,都是正常执行,cf3.get返回null
//anyOf是只有一个任务执行完成,无论是正常执行或者执行异常,都会执行cf3,cf3.get的结果就是已执行完成的任务的执行结果
CompletableFuture cf3 = CompletableFuture.allOf(cf,cf2).whenComplete((res,exception) -> {
if(Objects.nonNull(exception)){
exception.printStackTrace();
}else{
System.out.println("success,result:"+res);
}
});
System.out.println("main thread start,time:" + System.currentTimeMillis());
System.out.println("result:" + cf3.get());
System.out.println("main thread exit,time:" + System.currentTimeMillis());
}