CompletableFuture
一、Future接口
1、Future接口理论知识复习
Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
举例:
假设你点了一份外卖。你下单后,外卖员开始接单取餐(子线程开始执行任务),下单后你并不傻等在门口,而是继续看电影、打游戏(主线程继续做别的事情),等到你饿了 / 看完电影了,才去看看外卖送到了没有(调用 future.isDone()
判断任务是否完成),如果外卖已经送到,你就拿到结果了(调用 future.get()
获取结果),如果等太久不想等了,可以选择取消订单(调用 future.cancel()
取消任务)。
Future
概念一一对应
外卖流程 | Future 对应操作 |
---|---|
下单 -> 外卖员接单送餐 | 提交异步任务,返回 Future 对象 |
你继续看电影做其他事 | 主线程继续执行其他操作 |
时不时看手机查一下订单状态 | 调用 future.isDone() 查询是否完成 |
外卖送到,取餐 | 调用 future.get() 获取返回结果 |
发现时间太久取消订单 | 调用 future.cancel(true/false) 取消任务 |
2、Future接口常用实现类
2.1 Future接口能干什么
Future是Java5新加的一个接口,它提供一种异步并行计算的功能,如果主线程需要执行一个很耗时的计算任务,我们会就可以通过Future把这个任务放进异步线程中执行,主线程继续处理其他任务或者先行结束,再通过Future获取计算结果。
2.2 Future接口相关架构
- 目的:异步多线程任务执行且返回有结果,三个特点:多线程、有返回、异步任务(班长为老师去买水作为新启动的异步多线程任务且买到水有结果返回)
- 代码实现:Runnable接口+Callable接口+Future接口和FutureTask实现类。
/*** @author Guanghao Wei* @create 2023-04-10 11:21*/
public class CompletableFutureDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {FutureTask<String> futureTask = new FutureTask(new MyThread());Thread t1 = new Thread(futureTask); //开启一个异步线程t1.start();System.out.println(futureTask.get()); //有返回hello Callable}
}class MyThread implements Callable<String> {@Overridepublic String call() throws Exception {System.out.println("--------come in");return "hello Callable";}
}
简单理解一下:
首先要知道FutureTask是Future的实现类,而Future可以理解为存储一个异步任务结果的对象,他所实现的方法都是判断异步任务的执行结果、执行状态、是否取消执行等等。
FutureTask 为什么一般都要跟实现Callable接口的类一起使用呢?
因为实现callable接口的类都有返回值,而Future接口主要就是存储异步任务结果的接口,如果异步任务没有返回值,可以直接使用 Thread
或 Runnable,就不太需要Future类的参与了。
FutureTask 包裹实现 callable的类,在使用 new Thread 来开启一个线程。
所以使用步骤就跟上面是差不多的,使用
Thread 类的构造方法:
可以发现并没有传入 实现Callable类的 构造方法,Thread
的构造方法只接受 Runnable
或 Runnable
+ ThreadGroup,而new Thread(futureTask) thread 能接收futuretask就是因为它实现了Runnable
接口。这个其实是适配器模式,FutureTask
通过实现 Runnable
,让 Callable
能适配 Thread
,这是典型的适配器思想。
2.3 Future编码实战和优缺点分析
优点:Future+线程池异步多线程任务配合,能显著提高程序的运行效率。
public class FutureTaskTest {public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {ExecutorService executorService = Executors.newFixedThreadPool(3);long start = System.currentTimeMillis();FutureTask<String> task2 = new FutureTask<>(() -> {TimeUnit.SECONDS.sleep(2);return "2";});FutureTask<String> task1 = new FutureTask<>(() -> {TimeUnit.SECONDS.sleep(1);return "1";});FutureTask<String> task3 = new FutureTask<>(() -> {TimeUnit.SECONDS.sleep(3);return "3";});executorService.submit(task1);executorService.submit(task2);executorService.submit(task3);System.out.println(task1.get());System.out.println(task2.get(3,TimeUnit.SECONDS));while (true){if(task3.isDone()){System.out.println(task3.get());break;}else {TimeUnit.MILLISECONDS.sleep(200);}}System.out.println("执行耗时:"+(System.currentTimeMillis()-start));executorService.shutdown();}
}
缺点:
get()阻塞 -- 一旦调用get()方法求结果,一旦调用不见不散,非要等到结果才会离开,不管你是否计算完成,如果没有计算完成容易程序堵塞。
isDone()轮询---轮询的方式会耗费无谓的cpu资源,而且也不见得能及时得到计算结果,如果想要异步获取结果,
也就是说这两种方法都不友好,但是通常会以轮询的方式去获取结果,尽量不要阻塞。
结论:Future对于结果的获取不是很友好,只能通过阻塞或轮询的方式得到任务的结果。
/*** @author Guanghao Wei* @create 2023-04-10 11:41*/
public class FutureApiDemo {public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {FutureTask<String> futureTask = new FutureTask<>(() -> {System.out.println(Thread.currentThread().getName() + "--------come in");try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}return "task over";});Thread t1 = new Thread(futureTask, "t1");t1.start();// System.out.println(futureTask.get());//这样会有阻塞的可能,在程序没有计算完毕的情况下。System.out.println(Thread.currentThread().getName() + " ------忙其他任务");
// System.out.println(futureTask.get(3,TimeUnit.SECONDS));//只愿意等待三秒,计算未完成直接抛出异常while (true) {//轮询if(futureTask.isDone()){System.out.println(futureTask.get());break;}else{TimeUnit.MILLISECONDS.sleep(500);System.out.println("正在处理中,不要催了,越催越慢");}}/* 轮询结果* main ------忙其他任务t1--------come in正在处理中,不要催了,越催越慢正在处理中,不要催了,越催越慢正在处理中,不要催了,越催越慢正在处理中,不要催了,越催越慢正在处理中,不要催了,越催越慢正在处理中,不要催了,越催越慢正在处理中,不要催了,越催越慢正在处理中,不要催了,越催越慢正在处理中,不要催了,越催越慢正在处理中,不要催了,越催越慢task overProcess finished with exit code 0* */}
}
所以可以得出结论,对于简单的业务场景使用Future完全ok。
但是如果需要,我们想实现异步任务的回调通知、多个任务前后依赖、对计算速度选最快的等等这些需求时,Future接口提供的方法根本不够用,需要我们自己手动的实现。
二、CompletableFuture
1、CompletableFuture为什么会出现
上面说到了,Future接口的get方法会对主线程造成阻塞,阻塞的方式和异步编程的设计理念相违背的。而且如果我们使用 isDone()方法来检查是否完成,容易耗费cpu资源(cpu空转)。
对于真正的异步处理我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果,所以jdk8设计出CompletableFuture,CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。
2、CompletableFuture的架构
类架构说明:
可以发现我们的CompletableFuture 继承了 Future 接口 和 CompletionStage接口,Future接口不多说了,CompletionStage接口,顾名思义含有阶段的意思。代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段。一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发。
CompletableFuture 同时继承了这两个接口之后,就相当于即提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,也提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法它可能代表一个明确完成的Future,也可能代表一个完成阶段(CompletionStage),它支持在计算完成以后触发一些函数或执行某些动作。
3、