JUC并发编程 异步编程利器CompletableFuture 介绍
1 从FutureTask到CompletableFuture
1.1 Future
Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
举例:比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙完其他事情或者先执行完,过了一会再才去获取子任务的执行结果或变更的任务状态(老师上课时间想喝水,他继续讲课不结束上课这个主线程,让学生去小卖部帮老师买水完成这个耗时和费力的任务)。
1.2 Future接口常用实现类FutureTask异步任务
1.2.1 Future接口能干什么
Future是Java5新加的一个接口,它提供一种异步并行计算的功能,如果主线程需要执行一个很耗时的计算任务,我们会就可以通过Future把这个任务放进异步线程中执行,主线程继续处理其他任务或者先行结束,再通过Future获取计算结果。
1.2.2 Future接口相关架构
public class CompletableFutureDemo {
@SneakyThrows
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<>(new MyThread());
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
}
class MyThread implements Callable<String >{
@Override
public String call() throws Exception {
System.out.println("----come in call()");
return "hello Callable";
}
}
1.2.3 Future编码实战和优缺点分析
● 优点:Future+线程池异步多线程任务配合,能显著提高程序的运行效率。
● 缺点:
○ get()阻塞---一旦调用get()方法求结果,一旦调用不见不散,非要等到结果才会离开,不管你是否计算完成,如果没有计算完成容易程序堵塞。
○ isDone()轮询---轮询的方式会耗费无谓的cpu资源,而且也不见得能及时得到计算结果,如果想要异步获取结果,通常会以轮询的方式去获取结果,尽量不要阻塞。
● 结论:Future对于结果的获取不是很友好,只能通过阻塞或轮询的方式得到任务的结果。
1.2.3.1 get()阻塞演示
package com.kwfruit.thread.step01;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
@Slf4j
public class FutureAPIDemo {
@SneakyThrows
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<>(()->{
log.debug("[{}] ------come in",Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
return "task over";
});
Thread t1 = new Thread(futureTask);
t1.start();
/**
* 获取异步任务返回的结果值
* 必须拿到返回结果才能往下执行
*/
log.debug("futureTask 结果值:{}",futureTask.get());
//System.out.println(futureTask.get(3,TimeUnit.SECONDS));
System.out.println("嘻嘻嘻");
}
}
结论:一旦调用get()方法求结果,一旦调用不见不散,非要等到结果才会离开,不管你是否计算完成,如果没有计算完成容易程序堵塞
1.2.3.2 isDone()轮询
上面介绍了 用FutureTask get()方法获取返回值 会造成程序阻塞,如果线程没有执行完,没有返回结果,会一直等待下去。下面我们用isDone()方法配合get()方法进行优化,用打印日志的方式监控程序的执行状态。
package com.kwfruit.thread.step01;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
@Slf4j
public class FutureAPIDemo {
@SneakyThrows
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<>(()->{
log.debug("[{}] ------come in",Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
return "task over";
});
Thread t1 = new Thread(futureTask);
t1.start();
while (true){
if (futureTask.isDone()){
log.error("futureTask 的值为:[{}]",futureTask.get());
break;
}else{
try {
TimeUnit.MILLISECONDS.sleep(300);
}catch (InterruptedException e){
e.printStackTrace();
}
log.error("等待程序执行中......");
}
}
}
}
结论:轮询的方式会耗费无谓的cpu资源,而且也不见得能及时得到计算结果,如果想要异步获取结果,通常会以轮询的方式去获取结果,尽量不要阻塞
1.3 总结
1.3.1 对于简单的业务场景使用Future完全ok
1.3.2 回调通知:
应对Future的完成时间,完成了可以告诉我,也就是我们的回调通知
通过轮询的方式去判断任务是否完成这样非常占cpu并且代码也不优雅
1.3.3 创建异步任务:Future+线程池组合
1.3.4 多个任务前后依赖可以组合处理(水煮鱼--->买鱼--->调料--->下锅):
想将多个异步任务的结果组合起来,后一个异步任务的计算结果需要钱一个异步任务的值
想将两个或多个异步计算合并成为一个异步计算,这几个异步计算互相独立,同时后面这个又依赖前一个处理的结果
1.3.5 对计算速度选最快的:
当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果
1.3.6 结论:
使用Future之前提供的那点API就囊中羞涩,处理起来不够优雅,这时候还是让CompletableFuture以声明式的方式优雅的处理这些需求。
从i到i++
Future能干的,CompletableFuture都能干