概要
1.java8的CompletableFuture是没有提供超时处理的,而获取结果的get()和join()都是阻塞等待,所以通过ScheduledThreadPoolExecutor来实现计时器功能。java9以后有提供orTimeout()
2.异步方法(即带Async后缀的方法):可以选择是否传递线程池参数Executor运行在指定线程池中;当不传递Executor时,会使用ForkJoinPool中的共用线程池CommonPool(CommonPool的大小是CPU核数-1,如果是IO密集的应用,线程数可能成为瓶颈)
3.父子任务要做线程池隔离,避免发生死锁
相关API
函数
CompletableFutrue提供的三个相关函数
Supplier<U> // 生产者,没有入参,有返回结果
Consumer<T> // 消费者,有入参,但是没有返回结果
Function<T,U>// 函数,有入参,又有返回结果
依赖关系
thenApply():把前面任务的执行结果,交给后面的Function
thenCompose():用来连接两个有依赖关系的任务,结果由第二个任务返回(即第一个任务的输出是第二个任务的输入)
与关系
thenCombine():合并任务,有返回值
thenAccepetBoth():两个任务执行完成后,将结果交给thenAccepetBoth处理(需要两个任务的结果)
runAfterBoth():两个任务都执行完成后,执行下一步操作(不需要两个任务的结果)
allOf():需要所有给定的 CompletableFuture 完成
anyOf():只需任意一个给定的CompletablFuture完成
CompletableFuture<Void> future = future1.runAfterBoth(future2, () -> {
// 执行操作,不关心结果
});
CompletableFuture<Void> future = future1.thenAcceptBoth(future2, (result1, result2) -> {
// 使用 result1 和 result2 进行操作
});
或关系
applyToEither():两个任务哪个执行的快,就使用哪一个结果,有返回值
acceptEither():两个任务哪个执行的快,就消费哪一个结果,无返回值
runAfterEither():任意一个任务执行完成,进行下一步操作
获取结果
get():获取结果,需要显式处理可能抛出的 InterruptedException 和 ExecutionException(try-catch)
join():获取结果,如果异步任务抛出了异常,join()会将异常封装为CompletionException并抛出。
无论是get()还是join()都是阻塞获取结果的
//不需要显式捕获异常,获得结果的结果可以知道
task.join()
//显式
try{
task.get()
}catch(InterruptedException | ExecutionException e){
//执行操作
}
异常
exceptionally():主要用于处理异常情况,当 CompletableFuture 异步计算过程中发生异常时会调用 exceptionally() 方法
handle():用于处理正常结果和异常情况,它可以处理计算过程中的异常,也可以处理正常的计算结果
whenComplete():会在计算完成时执行,可以访问计算结果和异常信息,没有返回值。只是执行一些任务完成时需要的操作
应用
多元依赖(只等待任务完成,不做处理)
@LogAround("测试异步任务并行")
@GetMapping("/testA")
public void testA() {
ExecutorService executor = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
CompletableFuture<Integer> futureTask1 = supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
Integer result = dashboardService.getAccessTotalNumber();
return result;
}, executor);
ScheduledThreadPoolExecutor timeOutExecutor = new ScheduledThreadPoolExecutor(1);
CompletableFuture<Integer> futureTask2 = supplyAsync(() -> dashboardService.getUrlTotalNumber(), executor);
CompletableFuture<Integer> futureTask3 = supplyAsync(() -> dashboardService.getAccessTodayIpNumber(), executor);
CompletableFuture<Integer> futureTask4 = supplyAsync(() -> dashboardService.getAccessTodayNumber(), executor);
CompletableFuture<Void> allTask = allOf(futureTask1,futureTask2, futureTask3, futureTask4)
.applyToEither(TimeOutUtil.timeOut(timeOutExecutor, 0, TimeUnit.SECONDS), result -> result);
allTask.join();
}
超时处理工具类
package com.bluewind.shorturl.test.util;
import org.apache.poi.ss.formula.functions.T;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TimeOutUtil {
public static CompletableFuture timeOut(ScheduledThreadPoolExecutor threadPoolExecutor, long timeout, TimeUnit unit) {
//ScheduledThreadPoolExecutor是在指定时间后执行任务
CompletableFuture result = new CompletableFuture();
threadPoolExecutor.schedule(() -> {
System.out.println("========异步任务计时==========");
result.completeExceptionally(new Exception("异步任务执行超时了"));
}, timeout, unit);
return result;
}
}
调用allOf()是没有相关的函数可以获取全部任务的结果,需要通过get()和join()自己获取。并且allOf()返回值为CompletableFutrue<Void>,所以如果想要对所有任务的结果进行处理并且返回CompletableFutrue<T>需要新建一个相关对象来做后续处理,例子里写好了。如果不要求任务异步执行,可以看下面同步执行,用上一个结果的输入作为下一个结果的输出,但是同样的,不能多个任务,限制了只能两个任务。
多元依赖(等待任务完成,并对结果处理)
@LogAround("测试异步任务")
@GetMapping("/testB")
public void testB() {
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture<Integer> futureTask1 = supplyAsync(() -> {
//做其它事
Integer result = dashboardService.getAccessTotalNumber();
return result;
}, executor);
CompletableFuture<Integer> futureTask2 = supplyAsync(() -> dashboardService.getUrlTotalNumber(), executor);
CompletableFuture<Integer> futureTask3 = supplyAsync(() -> dashboardService.getAccessTodayIpNumber(), executor);
CompletableFuture<Integer> futureTask5 = supplyAsync(() -> dashboardService.getAccessTodayIpNumber(), executor);
CompletableFuture<Void> allTask = allOf(futureTask1, futureTask2, futureTask3);
CompletableFuture<Integer> futureTask4 = allTask.thenApplyAsync(v -> {
//做其它事(这里join一定不会阻塞,因为上面allOf了)
int result1 = futureTask1.join();
int result2 = futureTask2.join();
int result3 = futureTask3.join();
if (result1 < 0) {
throw new RuntimeException("查询出错");
}
return dashboardService.getAccessTodayNumber();
}, executor).exceptionally(ex -> {
Throwable t = ex.getCause(); //真正的异常封装在Throwable的cause属性里面
t.printStackTrace();
throw new RuntimeException(t);
});
Integer reuslt = futureTask4.join();
}
父子任务(线程池隔离、get()显式捕获异常)
@LogAround("测试异步任务父子任务")
@GetMapping("/testC")
public void testC() {
ExecutorService executor1 = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
ExecutorService executor2 = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
CompletableFuture<String> futureTask = supplyAsync(() -> {
//父任务
String sParent = String.valueOf(dashboardService.getAccessTotalNumber());
//子任务
CompletableFuture<Integer> futureTaskChild = supplyAsync(() -> dashboardService.getAccessTodayIpNumber(), executor2);
String sChild = String.valueOf(futureTaskChild.join());
return sParent + sChild;
}, executor1);
try {
futureTask.get(10, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException e) {
Throwable t = e.getCause(); //真正的异常封装在Throwable的cause属性里面
t.printStackTrace();
} catch (TimeoutException e) {
throw new RuntimeException(e);
}
}
上述例子中,在父任务里面还有一个子任务。这里要做线程池隔离。如果用同一个线程池,可能会出现线程池循环引用导致死锁(父任务获取到线程后,等到子任务获得线程完成执行,但是此时线程池中没有多余的线程,导致父任务得不到执行,如果此时主线程调用get()或者join()阻塞等待结果并且没有对执行时间做任何处理,主线程将会被阻塞在这里。
还要注意一点,CompletableFuture在回调方法中对异常进行了包装,如果要获取真正的异常,应该通过Throwable.getCause()获取
链式调用
@LogAround("链式执行")
@GetMapping("/testD")
public void testD() {
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture<Integer> futureTask1 = supplyAsync(() -> {
//做其它事
Integer result = dashboardService.getAccessTotalNumber();
return result;
}, executor);
CompletableFuture<Integer> futureTask2 = supplyAsync(() -> dashboardService.getUrlTotalNumber(), executor);
CompletableFuture<Integer> futureTask3 = supplyAsync(() -> dashboardService.getAccessTodayIpNumber(), executor);
CompletableFuture<Integer> allTask = futureTask1.thenCombineAsync(futureTask2,(reslut1,result2)->{
//执行操作,这里的return是给下一个thenCombine的
return "true";
},executor).thenCombineAsync(futureTask3,(result2, result3)->{
//执行操作,这里的return是最终返回值,要和CompletableFuture<T>类型匹配
return 1;
},executor).exceptionally(ex -> {
Throwable t = ex.getCause(); //真正的异常封装在Throwable的cause属性里面
t.printStackTrace();
throw new RuntimeException(t);
});;
Integer allReuslt = allTask.join();
}
一个thenCombineAsync只能接一个任务