一、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()); } }输出:

创作不易,谢谢你的赞和评论!!!精彩不断!!!!
1094

被折叠的 条评论
为什么被折叠?



