1.Future类简介
2.Future类的使用
3.CompletableFuture类的简介
4.CompletableFuture类的的使用
5.总结
1.Future类简介
我们之前在这篇文章 JAVA并发编程——Callable接口和FutureTask简介和使用中说过,Future就是可以获得返回值的一个异步处理线程类。
在之前无论是继承Thread类还是实现Runnable接口,我们始终无法得到线程的返回值,但是Future就可以,Future是拥有返回值的线程,这样说我们可能还不明白,我们用一个生产的案例来说明一下这个例子。
2.Future类的使用
假设我们现在有这样一个需求:
我们先来看一个页面:
这是一个网上找来的数据大屏的图片,如果这个屏幕的数据要你查询出并且返回,你会怎么做?
我们知道大屏展示的区域不同,要查询的业务逻辑也就不一样,可以将它分成如下这几块区域:
如果现在是一名新手,可能会一个区域一个区域串行地去查询,区域一查完了查区域二,紧接着区域三,然后再返回给前端,这样效率不高。
改进方法:多个区域同步查询,最后统一返回给前端(多箭齐发)。
这么说还是比较抽象,我们用一张图片举例:
用这张图片应该就能解释了,接下来我们使用Future类进行代码展示一下:
public DataResult queryData() throws ExecutionException, InterruptedException, TimeoutException {
DataResult result = new DataResult();
//查询区域1
FutureTask<Integer> allHumanNum = new FutureTask<>(() -> queryHumanNum());
//使用线程池执行
CalculateThreadPoolConfig.getThreadPoolExecutor().submit(allDoctorNum);
//查询区域2
FutureTask<Integer> allMaleNum = new FutureTask<>(() -> queryAllMaleNum());
//使用线程池执行 CalculateThreadPoolConfig.getThreadPoolExecutor().submit(queryInvitedDoctorNum);
//依次类推
//......
//拼接结果集
result.setAllHumanNum(allHumanNum.get(5, TimeUnit.SECONDS));
result.setAllMaleNum(allMaleNum.get(5, TimeUnit.SECONDS));
return result;
}
可以看出,我们无论查询多少个数据集,只用花费查询时间最长的那一个数据集的时间就够了,就实现了同步并行查询的效果,大大减少查询时间。
不过我们要注意一下这行代码:
allHumanNum.get(5, TimeUnit.SECONDS);
get的意思是一直阻塞返回结果,但是加了一个数字和单位就代表,只等待5秒,不然会抛异常。
我们工作的时候,如果需要调用别人的接口返回数据,但是不知道对方要计算多久,可能对方一个接口就把我们写的整个功能给拖垮。为了避免这种情况,工作中为了保护自己,我们需要学会加一层保险,如果对方的接口如果五秒没有返回就过时不候了。
3.CompletableFuture类的简介
介绍了前面的FutreTask类,我们知道这虽然是一个很好用的类,但是也是有很多的缺点的,比如:
1.get()容易阻塞
2.如果想要把多个结果集合并成一个结果集,就需要很多FutureTask
3.FutureTask只能用于单步的简单计算,不能将两个异步计算合成一个异步计算,这两个异步计算互相独立,同时第二个又依赖第一个的结果。
4.当Future集合中某个任务最快结束时,返回结果。
等等
缺点还是比较多的,这个时候我们就可以使用 CompletableFuture 类!
CompletableFuture弥补了Future模式的缺点。它可以将多个结果集合并成一个结果集,又可以将一个异步任务分成好几个阶段进行计算,后面的结果又依赖之前的结果等等。
总而言之,FutureTask能办到的事,CompletableFuture也能办到,同时CompletableFuture还能办到更多的事情!
我们先搜索一下CompletableFuture这个类
它实现了Future接口和CompletionStage接口。
CompletionStage:代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段。
我们仔细看CompletionStage拥有的方法,根据thenApply,whenComplete,exceptionally等方法,可以大概看出这个类可以对一个计算的每个阶段进行编程,先执行完一步,(根据thenApply)再执行下一步,(whenComplete)最后运行完了会怎么样,(exceptionally)抛出异常会怎么样等等。
好像就像一条调用链一样,这就是所谓的链式编程。
接下来我们用一个案例用CompletableFuture进行实战。
4.CompletableFuture类的的使用
假设我们现在有一个电商比价需求:
我们要从各大网站上收集Think in java这本书的价格,开多个线程从各大网站收集,收集完之后输出信息到控制台,线程执行完毕也要输出信息,请使用CompletableFuture和链式编程来进行解决。
我们接下来开始编程:
public class CompletableFutureNextMallDemo {
// 这是需要收集的价格网站
static List<NetMall> list = Arrays.asList(
new NetMall("jd"),
new NetMall("pdff"),
new NetMall("tmall"),
new NetMall("xhs"),
new NetMall("xy"),
new NetMall("zh"),
new NetMall("sf")
);
}
class NetMall {
// 这是网站的具体类以及计算价格的方法
private String mallName;
public NetMall() {
}
public NetMall(String mallName) {
this.mallName = mallName;
}
public String getMallName() {
return mallName;
}
public void setMallName(String mallName) {
this.mallName = mallName;
}
//计算价格的方法
public double calcPrice(String productName) {
try {
//假设这是计算过程
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
}
}
最关键的执行方法
//链式调度 多箭齐发
public static List<String> getPriceByASync(List<NetMall> list, String productName) {
//循环遍历所有网站,每一个网站开启一个线程进行计算
//CompletableFuture.supplyAsync代表有返回值的执行过程,相当于Future的get(0方法
return list.stream().map(netMall -> CompletableFuture.supplyAsync(() -> {
return String.format(productName + " in %s price is %.2f", netMall.getMallName(), netMall.calcPrice(productName));
}
//当第一步执行完毕后,第二步就是输出书的价格
).thenApply(r-> {
System.out.println(r);
return r;
}
//当完成任务之后,输出该网站计算完成
).whenComplete((v,e)-> {
System.out.println(netMall.getMallName()+"计算完成!");
}
//当抛出异常的时候,输出异常
).exceptionally(e->{
System.out.println("获取失败,出现异常!" );
e.printStackTrace();
return null;
})).collect(Collectors.toList())
.stream()
//最后统一调用join方法返回结果
.map(CompletableFuture::join)
//最后合并成一个List,作为结果返回
.collect(Collectors.toList());
}
这里我们将CompletableFuture抽离出来会发现代码会是如下所示:
CompletableFuture
.supplyAsync()//执行有返回值的异步处理,相当于FutureTask的get()
.thenApply()//接着执行第二步
.whenComplete()//当完成时触发的逻辑
.exceptionally();//当出现异常的时候触发的逻辑
5.总结
通过今天的学习,我们了解了Future和CompletableFuture的基本理论和实践,这本身就是非常好用的两个类,希望我们在工作中能多多使用,不过也不要盲目使用多线程,因为多使用一种工具,就会多一份风险。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。