一文讲清楚CompletableFuture,优化项目代码,提高查询效率
前言
CompletableFuture我很少用,但不排除要用到的情况。我个人觉得挺鸡肋的(可能是我业务场景有限制)。在成本够/项目大的情况我们可能会用队列。单体项目中我们会对一些接口进行拆分,用事件监听加异步的方式去实现。
为什么会用到异步编程
在一些单体项目中用户登录,注册后会查询一些用户信息组合后返回,比如基本信息,会员等级对应的权限,会员等级可以提供的优惠有哪些,根据用户点击记录推送一下喜欢的活动等(如果需要的话啊,登录后立马展示这种)。伪代码如下:
@Test
public void login() throws InterruptedException {
StopWatch stopWatch = new StopWatch();
stopWatch.start("task-name:登录用户!");
//用户登录-返回一些头像...基本信息
Thread.sleep(1000l);
stopWatch.stop();
//查询用户权限对应的菜单...菜单
stopWatch.start("task-name:查询用户权限对应的菜单!");
Thread.sleep(2000l);
stopWatch.stop();
System.out.println("stopWatch: " + stopWatch.prettyPrint());
System.out.println("总耗时:"+stopWatch.getTotalTimeMillis());
//组合菜单和基本信息到同一个对象并进行返回
//return;.... 等
}
我们看下打印结果
stopWatch: StopWatch '': running time = 3014869800 ns
---------------------------------------------
ns % Task name
---------------------------------------------
1009126200 033% task-name:登录用户!
2005743600 067% task-name:查询用户权限对应的菜单!
总耗时:3014
那么如何优化呢?我们先尝试最基本写法来优化。(简单使用)
首先我们先将上面的代码更改一下,将StopWatch用来记录整个方法耗时。
@Test
public void login2() {
StopWatch stopWatch = new StopWatch();
stopWatch.start("task-name:整个耗时!");
CompletableFuture<Map<String, Object>> infoFuture = CompletableFuture.supplyAsync(() -> {
try{
//用户登录-返回一些头像...基本信息
Thread.sleep(1000l);
}catch (InterruptedException e){
e.printStackTrace();
}
//在这里模拟一下基本信息并返回
Map<String, Object> infoMap = new HashMap();
infoMap.put("name", "张三");
infoMap.put("age", 18);
return infoMap;
});
//查询用户权限对应的菜单...菜单
CompletableFuture<Map<String, Object>> menuFuture = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
Map<String, Object> menuMap = new HashMap<String, Object>();
menuMap.put("menu", "首页,账户管理,积分管理");
return menuMap;
});
Map<String, Object> infoJoin = infoFuture.join();
Map<String, Object> menuJoin = menuFuture.join();
stopWatch.stop();
System.out.println("stopWatch: " + stopWatch.prettyPrint());
System.out.println("总耗时:"+stopWatch.getTotalTimeMillis());
//组合菜单和基本信息到同一个对象并进行返回
//return;.... 等
}
打印结果
stopWatch: StopWatch '': running time = 2007791800 ns
---------------------------------------------
ns % Task name
---------------------------------------------
2007791800 100% task-name:整个耗时!
总耗时:2007
第一节结果
我们可以看到同样的sleep时间,采用CompletableFuture后时间少了一些。
讲解
在上面的方法中,我们用到了CompletableFuture的两个方法。
第一个是: CompletableFuture的类的静态函数supplyAsync
该函数通过异步方式执行supplier提供的任务,并返回一个CompletableFuture对象,用于处理任务的结果。
第二个是: 方法join()
当调用 join() 方法时,如果 CompletableFuture 已经完成了计算,则直接返回结果;否则,它会阻塞当前线程直到计算完成。
如果 CompletableFuture 在完成时异常终止,join() 方法会抛出一个未经检查的异常(CompletionException),该异常封装了原始异常作为其原因。如果是由于取消而导致的异常,则会抛出 CancellationException。
相比于 get() 方法,join() 不会抛出 InterruptedException,因此在主线程不希望被中断的情况下,join() 可能是更方便的选择。但是,无论 join() 还是 get(),当 CompletableFuture 被取消或执行过程中抛出了异常,都会阻止程序继续执行,只是它们处理异常的方式略有不同。
第二节:深入了解
我们接着继续往下看,我们在上一阶段中使用了supplyAsync创建了异步线程。
其实在使用CompletableFuture创建异步任务时,一般有supplyAsync和runAsync两个方法:
创建异步任务的supplyAsync和runAsync
supplyAsync执行CompletableFuture任务,支持返回值。
runAsync执行CompletableFuture任务,没有返回值。
具体代码我们可点开这个类的源码查看。
//supplyAsync 方法
//使用默认内置线程池ForkJoinPool.commonPool(),根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
//自定义线程,根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
//runAsync 方法
//使用默认内置线程池ForkJoinPool.commonPool(),根据runnable构建执行任务
public static CompletableFuture<Void> runAsync(Runnable runnable)
//自定义线程,根据runnable构建执行任务
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
我直接上代码讲和注释,大家可以cv到idea上运行就明白。
supplyAsync
@Test
public void defaultSupplyAsync() {
//有返回值,该方法内部使用自己类中默认线程池对象
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() ->{
try {
Thread.sleep(5000l);
}catch (InterruptedException e){
e.printStackTrace();
}
return "complete";
});
String result = completableFuture.join();//阻塞,等待返回结果
log.info("方法执行完毕!");
System.out.println("result:" + result);
}
@Test
public void customSupplyAsync(){
//用自己的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletableFuture<String> callback = CompletableFuture.supplyAsync(()->{
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
return "complete";
}, executorService);
String result = callback.join();
System.out.println("result: "+result);
}
runAsync
@Test
public void defaultRunAsync(){
//采用这种方式,没有返回值,该方法内部使用自己类中默认线程池对象
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(5000l);
}catch (InterruptedException e){
e.printStackTrace();
}
log.info("方法执行完毕!");
});
completableFuture.join();
}
@Test
public void customRunAsync(){
ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletableFuture.runAsync(()->{
try{
Thread.sleep(5000l);
}catch (InterruptedException exception){
exception.printStackTrace();
}
}, executorService);
}