CompletableFuture
Future 和 CompletableFuture
Future 接口的不足
在使用Future执行异步任务时,存在以下几个缺点:
- Future不支持非阻塞调用,且只提供了 get() 方法来获取结果,在并发情况下只有等待所有线程执行完毕之后才能获取结果;通过轮询的方式调用 isDone() 方法判断是否执行完成容易导致cpu空转,造成系统资源浪费
- FutrueTask获取结果只能获取一次,如果需要多次访问计算结果则需要重新计算执行任务,会导致性能下降
- Future没有异常处理机制,其接口中没有处理异常的方法,如果执行任务过程中出现了异常,不好精准定位
- Future 不支持链式调用,也就是说第二个任务不能获取第一个任务执行后的结果再去执行
- Future只能计算单个任务,不支持多个Future合并,如果我们需要有10个Future合并,在执行所有Futrue任务完成之后再进行某些操作,是没法通过Future实现的
CompletableFuture 概述
CompletableFuture是Java 8引入的一个新特性,它提供了一种异步编程的方式,使得开发者能够编写非阻塞的代码。CompletableFuture实现了Future接口和CompletionStage接口,是对Future的功能增强 其类架构如下
CompletableFuture 有以下几个方面的特性:
- 异步执行:CompletableFuture 可以提交异步任务,使用CompletableFuture.runAsync() 方法执行没有返回值的异步任务,或者使用 CompletableFuture.supplyAsync() 方法执行有返回值的任务
- 回调函数:CompletableFuture 支持注册回调函数来处理异步任务完成时的结果。可以使用 thenApply()、thenAccepty、thenRun() 等方法在任务完成后执行相应的操作,并将上一步结果传给下一步
- 组合操作:CompletableFuture 提供了一系列的方法来组合多个CompletableFuture ,以实现复杂的异步操作链
- 异常处理:CompletabeFuture 允许对异常进行处理,可以使用exceptionally() 方法来捕获异常并返回一个默认值,或者使用handle() 方法来处理异常并返回一个新的CompletableFuture
- 超时处理:CompletableFuture 提供了CompleteOnTimeOut() 和 orTimeout() 方法,用于设置任务的超时时间,并在超时时执行相应的操作
CompletableFuture 的使用
创建一个异步任务 核心的四个静态方法
/**
* 有返回值的异步任务.
*
* @param runnable the action to run before completing the
* returned CompletableFuture
* @return the new CompletableFuture
*/
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
/**
* 有返回值的异步任务.
*
* @param runnable the action to run before completing the
* returned CompletableFuture
* @param executor the executor to use for asynchronous execution
* @return the new CompletableFuture
*/
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor){
return asyncSupplyStage(screenExecutor(executor), supplier);
}
/**
* 无返回值的异步任务
* @param runnable the action to run before completing the
* returned CompletableFuture
* @return the new CompletableFuture
*/
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(asyncPool, runnable);
}
/**
* 无返回值的异步任务
* @param runnable the action to run before completing the
* returned CompletableFuture
* @param executor the executor to use for asynchronous execution
* @return the new CompletableFuture
*/
public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor) {
return asyncRunStage(screenExecutor(executor), runnable);
}
常用方法
获得结果和触发计算
public T get()
public T get(long timeout,TimeUnit unit)
//和get一样的作用,只是不需要抛出异常
public T join()
//计算完成就返回正常值,否则返回备态值(传入的参数),立即获取结果不阻塞
public T getNow(T valuelfAbsent)
//是否打断get方法立即返回括号值
public boolean complete(T value)
对计算结果进行处理
thenApply:计算结果存在依赖关系,这两个线程串行化---->由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
public <U> CompletableFuture<U> thenApply(
Function<? super T,? extends U> fn) {
return uniApplyStage(null, fn);
}
handle:计算结果存在依赖关系,这两个线程串行化---->有异常也可以往下走一步
public <U> CompletableFuture<U> handle(
BiFunction<? super T, Throwable, ? extends U> fn) {
return uniHandleStage(null, fn);
}
使用案例演示
异步任务创建
无返回值的异步任务:runAsync
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
System.out.println("子线程线程:"+Thread.currentThread().getName()+" 启动干活");
TimeUnit.SECONDS.sleep(1);
System.out.println("子线程线程:"+Thread.currentThread().getName()+" 干活完成");
}catch (Exception e){
e.printStackTrace();
}
},executor);
System.out.println("获取到的结果是:"+completableFuture.get());
executor.shutdown();
}
有返回值的异步任务:supplyAsync
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(()->{
try {
System.out.println("子线程:"+Thread.currentThread().getName()+" 启动干活");
TimeUnit.SECONDS.sleep(1);
System.out.println("子线程:"+Thread.currentThread().getName()+" 干活结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "complete task";
},executor);
System.out.println("执行后的结果是:"+completableFuture.get());
executor.shutdown();
}
常用方法使用
对计算结果处理(thenApply和handle):
thenApply
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,new ArrayBlockingQueue<>(3));
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
System.out.println("子线程执行任务:"+Thread.currentThread().getName());
int d = 10/0;
return 10;
},executor).thenApply(v ->{
System.out.println(Thread.currentThread().getName()+":执行第二步处理:");
return v+2;
});
System.out.println(Thread.currentThread().getName() + "------主线程先去做其他事情");
System.out.println("获取到的最终执行结果是:"+future.get());
executor.shutdown();
}
执行结果:计算结果存在依赖关系,这两个线程串行化---->由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
子线程执行任务:pool-1-thread-1
main------主线程先去做其他事情
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 com.avgrado.demo.thread.CompleteFutureDemo.main(CompleteFutureDemo.java:23)
Caused by: java.lang.ArithmeticException: / by zero
at com.avgrado.demo.thread.CompleteFutureDemo.lambda$main$0(CompleteFutureDemo.java:16)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
handle:计算结果存在依赖关系,这两个线程串行化---->有异常也可以往下走一步
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,new ArrayBlockingQueue<>(3));
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
System.out.println("子线程执行任务:"+Thread.currentThread().getName());
int d = 10/0;
return 10;
},executor).handle((v,e)->{
System.out.println(Thread.currentThread().getName()+":执行第二步处理:");
return v+2;
});
System.out.println(Thread.currentThread().getName() + "------主线程先去做其他事情");
System.out.println("获取到的最终执行结果是:"+future.get());
executor.shutdown();
}
执行结果:
子线程执行任务:pool-1-thread-1
main:执行第二步处理:
main------主线程先去做其他事情
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NullPointerException
at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
at com.avgrado.demo.thread.CompleteFutureDemo.main(CompleteFutureDemo.java:23)
Caused by: java.lang.NullPointerException
at com.avgrado.demo.thread.CompleteFutureDemo.lambda$main$1(CompleteFutureDemo.java:20)
at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:822)
at java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:797)
at java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:837)
at java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2155)
at com.avgrado.demo.thread.CompleteFutureDemo.main(CompleteFutureDemo.java:18)
对计算结果进行消费(thenAccept):
消费处理结果,接受任务的处理结果,并消费处理,无返回结果
private static int calcuResult = 0;
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(()->{
try {
System.out.println("子线程:"+Thread.currentThread().getName()+" 启动干活");
TimeUnit.SECONDS.sleep(1);
System.out.println("子线程:"+Thread.currentThread().getName()+" 干活结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
calcuResult = 100;
return calcuResult;
},executor).thenAccept(t -> {
System.out.println("消费之前的结果是:"+calcuResult);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行消费线程是:"+Thread.currentThread().getName());
calcuResult --;
System.out.println("消费之后的结果是:"+calcuResult);
});
executor.shutdown();
}
执行结果:
子线程:pool-1-thread-1 启动干活
子线程:pool-1-thread-1 干活结束
消费之前的结果是:100
执行消费线程是:pool-1-thread-1
消费之后的结果是:99
异常处理 (exceptionally)
在任务执行时出现异常触发
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
int num = 1/0;
System.out.println("执行其他任务开始");
return 0;
},executor).exceptionally(e ->{
System.out.println("获取到的异常信息是:"+e.getMessage());
return -1;
});
executor.shutdown();
}
执行结果:
获取到的异常信息是:java.lang.ArithmeticException: / by zero
结果合并(thenCompose、thenCombine、allOf、anyOf)
thenCompose:合并两个有依赖关系的 CompletableFutures 的执行结果
private static int num = 0;
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(()->{
try {
System.out.println("子线程执行任务开始"+Thread.currentThread().getName());
num ++;
TimeUnit.SECONDS.sleep(2);//这里模拟第一个任务执行需要耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
return num;
},executor);
CompletableFuture<Integer> f2 = f1.thenCompose(i->
CompletableFuture.supplyAsync(()->{
System.out.println("合并线程执行任务开始"+Thread.currentThread().getName());
return i +11;
},executor)
);
System.out.println("第一个结果:"+f1.get());
System.out.println("两个合并的结果:"+f2.get());
executor.shutdown();
}
执行结果:
子线程执行任务开始pool-1-thread-1
第一个结果:1
---这里会等待第一个任务执行完成后才会执行合并的任务---
.........
合并线程执行任务开始pool-1-thread-2
两个合并的结果:12
thenCombine:合并两个没有依赖关系的 CompletableFutures 任务
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
Instant start = Instant.now();
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(()->{
int num = 0;
try {
System.out.println("子线程执行任务开始"+Thread.currentThread().getName());
num ++;
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return num;
},executor);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(()->{
int num = 0;
System.out.println("子线程执行任务开始"+Thread.currentThread().getName());
num =num +20;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return num;
},executor);
CompletableFuture<Integer> combineResult = f1.thenCombine(f2, new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer a, Integer b) {
System.out.println("合并计算:"+Thread.currentThread().getName());
return a+b;
}
});
System.out.println("第一个结果:"+f1.get());
System.out.println("第二个结果:"+f2.get());
Instant end = Instant.now();
System.out.println("任务执行耗时:"+Duration.between(start,end).toMillis());
System.out.println("两个合并的结果:"+combineResult.get());
executor.shutdown();
}
任务执行结果:
子线程执行任务开始pool-1-thread-1
子线程执行任务开始pool-1-thread-2
第一个结果:1
第二个结果:20
合并计算:pool-1-thread-2
2100
两个合并的结果:21
allOf: 一系列独立的 future 任务,等其所有的任务执行完后做一些事情
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("主线程开始");
List<CompletableFuture> list = new ArrayList<>();
CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
try{
int num =10;
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()+":加 10 任务开始");
num += 10;
return num;
}catch (Exception e){
return 0;
}
});
list.add(job1);
CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
try{
int num =10;
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+":乘以 10 任务开始");
num = num * 10;
return num;
}catch (Exception e){
return 1;
}
});
list.add(job2);
CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() -> {
try{
int num =10;
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+":减以 10 任务开始");
num = num * 10;
return num;
}catch (Exception e){
return 2;
}
});
list.add(job3);
CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() -> {
try{
int num =10;
Thread.sleep(4000);
System.out.println(Thread.currentThread().getName()+":除以 10 任务开始");num = num * 10;
return num;
}catch (Exception e){
return 3;
}
});
list.add(job4);
//多任务合并后输出执行的结果
List<Integer> collect = list.stream().map(CompletableFuture<Integer>::join).collect(Collectors.toList());
System.out.println(collect);
//直接使用get获取是获取不到结果的
CompletableFuture<Void> f = CompletableFuture.allOf(job1, job2, job3, job4);
System.out.println("直接使用get获取结果:"+f.get());
执行结果:
===========这里没有使用自定义的线程池,所以使用默认的ForkJoinPool线程池================
主线程开始
ForkJoinPool.commonPool-worker-2:乘以 10 任务开始
ForkJoinPool.commonPool-worker-3:减以 10 任务开始
ForkJoinPool.commonPool-worker-4:除以 10 任务开始
ForkJoinPool.commonPool-worker-1:加 10 任务开始
[20, 100, 100, 100]
直接使用get获取结果:null
anyOf:只要在多个 future 里面有一个返回,整个任务就可以结束,而不需要等到每一个
future 结束
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("主线程开始");
CompletableFuture<Integer> [] futures = new CompletableFuture[4];
CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
try{
int num =10;
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()+":加 10 任务开始");
num += 10;
return num;
}catch (Exception e){
return 0;
}
});
futures[0]= job1;
CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
try{
int num =10;
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+":乘以 10 任务开始");
num = num * 10;
return num;
}catch (Exception e){
return 1;
}
});
futures[1]= job2;
CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() -> {
try{
int num =10;
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+":减以 10 任务开始");
num = num * 10;
return num;
}catch (Exception e){
return 2;
}
});
futures[2]= job3;
CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() -> {
try{
int num =10;
Thread.sleep(4000);
System.out.println(Thread.currentThread().getName()+":除以 10 任务开始");num = num * 10;
return num;
}catch (Exception e){
return 3;
}
});
futures[03]= job4;
CompletableFuture<Object> future = CompletableFuture.anyOf(futures);
System.out.println("直接使用get获取结果:"+future.get());
执行结果:
主线程开始
ForkJoinPool.commonPool-worker-2:乘以 10 任务开始
直接使用get获取结果:100
完成时触发(whenComplete)
private static int num = 10;
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,new ArrayBlockingQueue<>(3));
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
try {
num++;
System.out.println("任务执行开始:"+Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return num;
},executor).whenComplete((v,e)->{
System.out.println("触发完成时事件");
if (e == null) {
System.out.println("计算完成 更新系统" + v);
}
//此处增加一行触发异常代码
int i = 1/0;
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("异常情况:" + e.getCause() + " " + e.getMessage());
return null;
}
);
executor.shutdown();
}
执行结果:
任务执行开始:pool-1-thread-1
触发完成时事件
计算完成 更新系统11
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:292)
at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:308)
at java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:769)
at java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:736)
at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:474)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1595)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.ArithmeticException: / by zero
at com.avgrado.demo.thread.CompleteFutureDemo.lambda$main$1(CompleteFutureDemo.java:40)
at java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:760)
... 6 more
异常情况:java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero