2.1 Future接口理论知识复习
Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
举例:比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙完其他事情或者先执行完,过了一会再才去获取子任务的执行结果或变更的任务状态(老师上课时间想喝水,他继续讲课不结束上课这个主线程,让学生去小卖部帮老师买水完成这个耗时和费力的任务)。
2.2 Future接口常用实现类FutureTask异步任务
2.2.1 Future接口能干什么
Future是Java5新加的一个接口,它提供一种异步并行计算的功能,如果主线程需要执行一个很耗时的计算任务,我们会就可以通过Future把这个任务放进异步线程中执行,主线程继续处理其他任务或者先行结束,再通过Future获取计算结果。
2.2.2 Future接口相关架构
● 目的:异步多线程任务执行且返回有结果,三个特点:多线程、有返回、异步任务(班长为老师去买水作为新启动的异步多线程任务且买到水有结果返回)
● 代码实现:Runnable接口+Callable接口+Future接口和FutureTask实现类。
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> {
@Override
public String call() throws Exception {
System.out.println("--------come in");
return "hello Callable";
}
}
2.2.3 Future编码实战和优缺点分析
● 优点: Future+线程池异步多线程任务配合,能显著提高程序的运行效率。
● 缺点:
○ get()阻塞—一旦调用get()方法求结果,一旦调用不见不散,非要等到结果才会离开,不管你是否计算完成,如果没有计算完成容易程序堵塞。
○ isDone()轮询—轮询的方式会耗费无谓的cpu资源,而且也不见得能及时得到计算结果,如果想要异步获取结果,通常会以轮询的方式去获取结果,尽量不要阻塞。
● 结论:Future对于结果的获取不是很友好,只能通过阻塞或轮询的方式得到任务的结果。
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 over
Process finished with exit code 0
* */
}
}
2.2.4 完成一些复杂的任务
● 对于简单的业务场景使用Future完全ok
● 回调通知:
○ 应对Future的完成时间,完成了可以告诉我,也就是我们的回调通知
○ 通过轮询的方式去判断任务是否完成这样非常占cpu并且代码也不优雅
● 创建异步任务:Future+线程池组合
● 多个任务前后依赖可以组合处理(水煮鱼—>买鱼—>调料—>下锅):
○ 想将多个异步任务的结果组合起来,后一个异步任务的计算结果需要钱一个异步任务的值
○ 想将两个或多个异步计算合并成为一个异步计算,这几个异步计算互相独立,同时后面这个又依赖前一个处理的结果
● 对计算速度选最快的:
○ 当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果
● 结论:
○ 使用Future之前提供的那点API就囊中羞涩,处理起来不够优雅,这时候还是让CompletableFuture以声明式的方式优雅的处理这些需求。
○ 从i到i++
○ Future能干的,CompletableFuture都能干
2.3 CompletableFuture对Future的改进
2.3.1 CompletableFuture为什么会出现
● get()方法在Future计算完成之前会一直处在阻塞状态下,阻塞的方式和异步编程的设计理念相违背。
● isDene()方法容易耗费cpu资源(cpu空转),
● 对于真正的异步处理我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果
jdk8设计出CompletableFuture,CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。
2.3.2 CompletableFuture和CompletionStage介绍
类架构说明:
2.3.3 核心的四个静态方法,来创建一个异步任务
四个静态构造方法
对于上述Executor参数说明:若没有指定,则使用默认的ForkJoinPoolcommonPool()作为它的线程池执行异步代码,如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码
public class CompletableFutureBuildDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
},executorService);
System.out.println(completableFuture.get()); //null
CompletableFuture<String> objectCompletableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello supplyAsync";
},executorService);
System.out.println(objectCompletableFuture.get());//hello supplyAsync
executorService.shutdown();
}
}
CompletableFuture减少阻塞和轮询,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。
public class CompletableFutureUseDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "---come in");
int result = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (result > 5) { //模拟产生异常情况
int i = 10 / 0;
}
System.out.println("----------1秒钟后出结果" + result);
return result;
}, executorService).whenComplete((v, e) -> {
if (e == null) {
System.out.println("计算完成 更新系统" + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("异常情况:" + e.getCause() + " " + e.getMessage());
return null;
});
System.out.println(Thread.currentThread().getName() + "先去完成其他任务");
executorService.shutdown();
}
}
/**
* 无异常情况
* pool-1-thread-1---come in
* main先去完成其他任务
* ----------1秒钟后出结果9
* 计算完成 更新系统9
*/
/**
* 有异常情况
*pool-1-thread-1---come in
* main先去完成其他任务
* java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
* 异常情况:java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero
*/
CompletableFuture优点:
● 异步任务结束时,会自动回调某个对象的方法
● 主线程设置好回调后,不用关心异步任务的执行,异步任务之间可以顺序执行
● 异步任务出错时,会自动回调某个对象的方法