在一些简单的业务场景使用Future创建异步任务是完全足够的。当业务场景比较复杂时,推荐使用CompletableFuture。
CompletableFuture同时实现了Future、CompletionStage两个接口。CompletionStage代表异步计算过程的某一个阶段,一个阶段完成后则会触发另一个阶段。
一、CompletableFuture与Future的对比
1.获取异步任务的结果
CompletableFuture可通过回调方法在异步任务完成后回调whenComplete()方法主动进行下一步操作,而Future则只能通过阻塞或轮询的方式获取异步返回值(调用get()方法会导致线程阻塞与异步思想相悖、轮询则会消耗无谓的CPU资源)。
2.组合多个异步任务
在当前异步任务需要调用上一个异步任务的结果时,CompletableFuture可将这多个异步任务组合成一个任务,而Future则只能创建多个任务并以阻塞的方式获取上一个异步任务的结果供下一个任务调用。
3.返回最快的处理结果
当Future集合中某个任务结束最快时,返回最快的处理结果。
二、代码实战
1.CompletableFuture的四种构建方式
//创建没有返回值的CompletableFuture
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
//创建有返回值的CompletableFuture
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
在不指定线程池的方法中,直接使用默认的ForkJoinPool.commonPool()作为它的线程池执行异步代码。
2.CompletableFuture的基本使用
使用无返回值的方式构建一个CompletableFuture。
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, threadPool);
System.out.println(completableFuture.get());
threadPool.shutdown();
}
}
使用带返回值的方式构建一个CompletableFuture。
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello CompletableFuture";
});
System.out.println(completableFuture.get());
threadPool.shutdown();
}
}
使用CompletableFuture的回调函数实现任务的集成。由于CompletableFuture类似于一个守护线程,所以在主线程完成后守护线程会自动停止,这种情况会因为主线程执行结束导致CompletableFuture线程执行到一半就停止。因此建议使用线程池构建CompletableFuture线程。
public class CompletableFutureStageDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
int a = 10;
int b = 2;
return a / b;
}, threadPool).whenComplete((v,e) -> {//当上一步操作完成后调用该方法。v表示上一步的结果,e表示异常。
int c = 5;
System.out.println(v + c);//10
}).exceptionally(e -> {//出现异常时调用该方法
e.printStackTrace();
return 0;
});
System.out.println("主线程任务");
threadPool.shutdown();
}
}
3.CompletableFuture的其它方法
- jion()方法:该方法与get()方法功能一致,不同的是get()方法在编译期间需要处理异常,而join()方法在编译期间不会报异常。
- getNow(String ValueIfAbsent)方法:该方法需要传递一个String类型的参数,在调用该方法时,若还未计算出结果,则会直接返回ValueIfAbsent的值。
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "Hello CompletableFuture"; }); //在结果还未计算出时,使用缺省值 System.out.println(completableFuture.getNow("please waiting")); //运行后会输出please waiting
- complete(T value)方法: 该方法会先判断get()方法是否阻塞,若不阻塞,则会返回false,调用get()方法会返回计算出的值;若get()阻塞,则该方法会打断get()方法,返回true,调用get()方法则获取到参数value的值。
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "Hello CompletableFuture"; }); //在此处调用get()方法阻塞,complete()方法会打断get返回true System.out.println(completableFuture.complete("return value")); //调用get()方法会直接返回return value System.out.println(completableFuture.get()); //运行结果 // true return value
- thenApply()方法:该方法则会获取上一步的执行结果并返回另一个结果,用于复杂业务场景中的下一步需要上一步数据的多步骤操作。当前步骤报错就会停止。
public class CompletableFutureStageDemo { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(3); CompletableFuture.supplyAsync(() -> { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("开始处理"); return 1; }, threadPool).thenApply(f -> { System.out.println("add 1"); return f + 1; }).thenApply(f -> { System.out.println("add 2"); return f + 2; }).whenCompleteAsync((v,e) -> { System.out.println("输出结果 --> " + v); }).exceptionally(e -> { System.out.println("出现异常"); return 0; }); System.out.println("主线程任务"); threadPool.shutdown(); } } //输出 主线程任务 开始处理 add 1 add 2 输出结果 --> 4
- handle()方法:与thenApply方法作用一致,区别就是thenApply()方法在遇到异常时就会停止,而handle()方法则会继续向下一步走。
public class CompletableFutureStageDemo { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(3); CompletableFuture.supplyAsync(() -> { System.out.println("开始处理"); return 1; }, threadPool).handle((f,e) -> { System.out.println("add 1"); int i = 10 / 0; return f + 1; }).handle((f,e) -> { System.out.println("add 2"); return f + 2; }).whenCompleteAsync((v,e) -> { System.out.println("输出结果 --> " + v); }).exceptionally(e -> { System.out.println("出现异常"); return 0; }); threadPool.shutdown(); } } //输出 开始处理 add 1 add 2 输出结果 --> null 出现异常
- thenAccept()方法:该方法会传入上一个流程的结果,并无返回值。
public class CompletableFutureStageDemo { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(3); CompletableFuture.supplyAsync(() -> { System.out.println("开始处理"); return 1; }, threadPool).thenAccept((c) -> { System.out.println("输出结果 --> " + c); }); threadPool.shutdown(); } } //输出 开始处理 输出结果 --> 1
- applyToEither()方法:该方法会返回两个CompletableFuture中最快完成的那一个的结果。
public class CompletableFutureStageDemo { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(3); CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> { System.out.println("A coming"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace();} return "A"; }); CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> { System.out.println("B coming"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace();} return "B"; }); CompletableFuture<String> future = futureA.applyToEither(futureB, f -> { System.out.println(f + " is winner"); return f + " is winner"; }); threadPool.shutdown(); } } //输出 A coming B coming B is winner
- thenCombine()方法:该方法可将两个CompletableFuture的结果合并,当其中一个完成时会等待另一个执行完成。
public class CompletableFutureStageDemo { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(3); CompletableFuture<Integer> futureA = CompletableFuture.supplyAsync(() -> { System.out.println("流程1"); return 10; }); CompletableFuture<Integer> futureB = CompletableFuture.supplyAsync(() -> { System.out.println("流程2"); return 10; }); CompletableFuture<Integer> future = futureA.thenCombine(futureB, (x, y) -> { System.out.println("开始合并"); return x + y; }); System.out.println(future.join()); threadPool.shutdown(); } } //结果 流程1 流程2 开始合并 20
知识点:调用thenRun()与thenRunAsync()时的线程池区别,调用thenRun()方法时会与supplyAsync共用同一个线程池,调用thenRunAsync()方法时则会判断cpu内核数是否大于1,若大于1则会使用默认的ForkJoinPool线程池。