一、CompletableFuture类
同传统的 Future 相比较:
- CompletableFuture 能够主动设置计算的结果值、主动终结计算过程,从而在某些场景下主动结束阻塞等待。
- 而 Future 由于不能主动设置计算结果值,一旦调用 get() 方法进行阻塞等待,要么当计算结果产生,要么超时,才会返回。
先看个简单的示例:CompletableFuture 是如何主动完成、被动完成。
public class Test {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
try{
Thread.sleep(1000L); //模拟耗时任务
return "test";
} catch (Exception e){
return "failed test";
}
});
//情况1:
System.out.println(future.join());
future.complete("manual test");
//情况2:
future.complete("manual test");
System.out.println(future.join());
}
}
complete()
方法会尝试完成未完成的 CompletableFuture
,如果异步任务已经完成,complete()
方法则不会有影响。
① 情况1:
future.join()
:join()
方法会等待异步任务完成并获取结果。由于异步任务执行需要 1 秒时间,因此主线程会阻塞 1 秒,等待任务返回"test"
,此时输出 testfuture.complete("manual test")
:complete("manual test")
尝试手动完成future
,但由于任务已经完成,所以complete()
不会有任何效果。
② 情况2:
-
future.complete("manual test")
:在调用join()
之前先调用了complete("manual test")
。此时,异步任务可能还没有完成,因为主线程立即执行了complete()
,而异步任务还在等待 1 秒。由于任务还未完成,complete()
会手动完成CompletableFuture
,并设置其结果为"manual test"
,此时输出 manual test -
future.join()
:当调用join()
时,由于complete()
已经手动完成了CompletableFuture
,因此join()
会立即返回"manual test"
,而不会等待异步任务。异步任务的结果将被忽略,因为complete()
先行完成了CompletableFuture
。
二、创建 CompletableFuture 对象
1、构造器创建
CompletableFuture<String> future = new CompletableFuture();
String result = future.join();
System.out.println(result);
当前线程会一直阻塞在这里。此时,如果在另外一个线程中,主动设置该 CompletableFuture 的值(比如:future.complete("test");
,则上面线程中的结果就能返回 test。
2、supplyAsync() 方法创建
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
第一种:只需传入一个 Supplier 实例(一般使用 lamda 表达式),此时框架会默认使用 ForkJoin 的线程池来执行被提交的任务。
第二种:可以指定自定义的线程池,然后将任务提交给该线程池执行。
CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
System.out.println("compute test");
return "test";
});
String result = future.join();
System.out.println("get result: " + result);
//输出:get result: test
3、runAsync() 方法创建
与 supplyAsync() 不同的是,runAsync() 传入的任务要求是 Runnable 类型的,所以没有返回值。因此,runAsync 适合创建不需要返回值的计算任务。同 supplyAsync() 类似,runAsync() 的方式也有两种。
CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
System.out.println("compute test");
});
System.out.println("get result: " + future.join());
//输出:由于任务没有返回值, 所以最后的打印结果是 "get result: null"
三、组合多个异步任务
1、allOf() 方法:等待多个 CompletableFuture
全部完成后再执行某些操作,它返回一个新的 CompletableFuture<Void>
,表示当所有提供的 CompletableFuture
都完成时,这个新的 CompletableFuture
也会完成。
public class Test {
public static void main(String[] args) {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
// 使用 allOf 组合多个异步任务
CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(future1, future2);
// 阻塞,直到所有异步任务完成
allOfFuture.join();
// 输出结果
System.out.println(future1.join()); // 输出: Task 1
System.out.println(future2.join()); // 输出: Task 2
}
}
为什么还需要:allOfFuture.join();
阻塞?
① CompletableFuture.allOf()
本身不会主动等待任务完成(也就是并不会阻塞主线程)。你必须显式地调用它的 等待方法,比如 join()
或 get()
,来等待所有任务的完成,否则主线程可能会在异步任务尚未完成之前继续执行。
② 为什么上面案例不需要 allOfFuture.join();
也可以?因为主线程没有任务输出,唯一的输出就是 System.out.println(future1.join());
但是其中 future1.join()
会阻塞等待任务完成(future1.get()
效果也是一样的) ,所以要不要含义是一样的。
还没理解?再来看个例子:
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Task 1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(future1, future2);
System.out.println("所有任务开始执行,但未必都已经完成");
// 主线程会被 allOfFuture.join() 阻塞,直到 future1 和 future2 完成
allOfFuture.join(); // 这一行确保等待所有任务完成
try {
// 现在可以安全地获取异步任务的结果
System.out.println(future1.get());
System.out.println(future2.get());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("所有任务都完成,继续执行主线程的其他逻辑");
}
}
输出:
注意: 还有个用法
CompletableFuture.allOf(future1, future2,.....).get(20, TimeUnit.SECONDS);
get 方法会阻塞当前线程,直到所有传入的 CompletableFuture 都完成,或者指定的超时时间(这里是20秒)到达。如果超时,它会抛出 TimeoutException。为什么这么做?----> 就是不要一直阻塞等待,因为上面这些任务可能执行时间长(比如:需要远程调用),假如在高并发的情况下,一直阻塞等待的话,则线程数会非常多,容易造成内存溢出。
2、anyOf()
:只要其中一个 CompletableFuture
完成,就返回结果。
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Task 1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(future1, future2);
System.out.println("所有任务开始执行,但未必都已经完成");
// 主线程会被 anyOfFuture.join() 阻塞,直到至少有一个完成
System.out.println(anyOfFuture.join());
System.out.println("所有任务都完成,继续执行主线程的其他逻辑");
try {
// 现在可以安全地获取异步任务的结果
System.out.println(future1.get());
System.out.println(future2.get());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("所有任务都完成,继续执行主线程的其他逻辑");
}
}
输出:
四、链式异步编程
CompletableFuture
支持链式调用,多个任务之间,可以前后相连,从而形成一个计算流。比如:任务1产生的结果,可以直接作为任务2的入参,参与任务2的计算,以此类推。常见的链式操作包括:
thenApply/thenApplyAsync、thenAccept/thenAcceptAsync、thenRun/thenRunAsync
thenCombine/thenCombineAsync、thenCompose/thenComposeAsync
带 Async 用法一样,但是参数多传一个,表示指定任务使用的具体线程池。
-
thenApply()
:用于对异步计算的结果进行后续处理。CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { return "Hello"; }).thenApply(result -> { return result + " World"; }); System.out.println(future.join()); // 输出: Hello World
再比如:有任务A,还有任务B。任务B需要在任务A执行完毕后再执行。而且任务B需要任务A的返回结果。任务B自身也有返回结果。
public static void main(String[] args) { CompletableFuture<String> taskA = CompletableFuture.supplyAsync(() -> { String id = UUID.randomUUID().toString(); System.out.println("执行任务A:" + id); return id; }); CompletableFuture<String> taskB = taskA.thenApply(result -> { System.out.println("任务B获取到任务A结果:" + result); result = result.replace("-", ""); return result; }); System.out.println("main线程拿到结果:" + taskB.join()); }
-
thenAccept()
:是一个消费者方法,它接收任务的结果并进行处理,没有返回值。CompletableFuture.supplyAsync(() -> { return "Hello"; }).thenAccept(result -> { System.out.println("Result: " + result); // 输出: Result: Hello });
-
thenRun()
:前置任务没有返回结果,后置任务不接收前置任务结果,后置任务也没有返回结果。public static void main(String[] args) throws IOException { CompletableFuture.runAsync(() -> { System.out.println("任务A!!"); }).thenRun(() -> { System.out.println("任务B!"); }); }
注意:通过 thenApply / thenAccept / thenRun
连接的任务,当且仅当前置任务计算完成时,才会开始后置任务的计算。因此,这组函数主要用于连接前后有依赖的任务链。
-
thenCombine()
:用于并行执行两个异步任务,并将它们的结果合并,什么意思?也就是:同前面一组连接函数相比,thenCombine 最大的不同是连接任务可以是独立的,从而允许前后连接的两个任务可以并行执行(后置任务不需要等待前置任务执行完成),最后当两个任务均完成时,再将其结果同时传递给下游处理任务,从而得到最终结果。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1"); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2"); CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> { return result1 + " + " + result2; }); System.out.println(combinedFuture.join()); // 输出: Task 1 + Task 2
注意:一般,在连接任务之间互相不依赖的情况下,可以使用 thenCombine 来连接任务,从而提升任务之间的并发度。
-
thenCompose()
:前面讲了 thenCombine 主要用于没有前后依赖关系之间的任务进行连接。那么,如果两个任务之间有前后依赖关系,但是连接任务又是独立的 CompletableFuture,该怎么实现呢?
先来看一下直接使用 thenApply 来实现:
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(()->{ System.out.println("compute 1"); return 1; }); CompletableFuture<CompletableFuture<Integer>> future2 = future1.thenApply(r -> CompletableFuture.supplyAsync(() -> r + 10)); System.out.println(future2.join().join());
可以发现,上面示例代码中,future2 的类型变成了 CompletableFuture 嵌套,而且在获取结果的时候,也需要嵌套调用 join 或者 get。这样,当连接的任务越多时,代码会变得越来越复杂,嵌套获取层级也越来越深。因此,需要一种方式,能将这种嵌套模式展开,使其没有那么多层级。
看一下通过 thenCompose 如何实现上面的代码:
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(()->{ System.out.println("compute 1"); return 1; }); CompletableFuture<Integer> future2 = future1.thenCompose(r -> CompletableFuture.supplyAsync(() -> r + 10)); System.out.println(future2.join());
也就是允许你将多个
CompletableFuture
串联起来,第二个任务依赖于第一个任务的结果。//这里再以链式调用的方式举个例子: CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { return "Task 1"; }).thenCompose(result -> CompletableFuture.supplyAsync(() -> { return result + " + Task 2"; })); System.out.println(future.join()); // 输出: Task 1 + Task 2
五、异常处理
CompletableFuture
提供了对异常的处理机制,类似于 try-catch:
-
exceptionally()
:只有前面业务执行时出现异常了,才会执行当前方法来处理。注意:只有异常出现时、任务没有处理完时,才会触发。CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { if (true) { throw new RuntimeException("Something went wrong"); } return "Success"; }).exceptionally(ex -> { return "exception: " + ex.getMessage(); }); System.out.println(future.join()); // 输出: exception: Something went wrong
-
whenComplete()
:可以拿到返回结果,同时也可以拿到出现的异常信息,但不能返回结果(也就是没有return)public class Test { public static void main(String[] args) { CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(()->{ System.out.println("compute 1"); return 1; }); //也可以以链式调用的形式写 //r:前置任务的处理结果 e:前置任务的异常,没有为null CompletableFuture future2 = future1.whenComplete((r, e)->{ if(e != null){ System.out.println("compute failed!"); //不能有return语句 } else { System.out.println("received result is " + r); } }); System.out.println("result: " + future2.join()); } }
输出:需要注意的是,future2 获得的结果是前置任务的结果,whenComplete 中的逻辑不会影响计算结果。
-
handle()
:与 whenComplete 的作用有些类似,但是 handle 有返回值,而且返回值会影响最终获取的计算结果。public class Test { public static void main(String[] args) { CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> { System.out.println("compute 1"); return 1; }); CompletableFuture<Integer> future2 = future1.handle((r, e) -> { if (e != null) { System.out.println("compute failed!"); //有返回语句 return r; } else { System.out.println("received result is " + r); return r + 10; } }); System.out.println("result: " + future2.join()); } }
输出:
创作不易,谢谢你的赞和评论!!!精彩不断!!!!