概要
众所周知,CompletableFuture
随着Java8的诞生粉墨登场,这个类提供了非常强大的Future
的扩展功能,可以大大简化我们进行异步编程时的复杂性以及代码的繁琐性,而且还是函数式的编码方式,可以通过各种静态方法对多个任务进行转换、计算以及组合最终收集;当然也可以进行任务的超时重试,这样就避开了对spring-retry
以及其他框架的依赖引入,既可以用来学习领悟,也能进行简单的任务超时重试处理。
前置知识
-
CompletableFuture
,本站有很多优秀文章,如下推荐一篇很喜欢的 -
线程池基础知识,以及对定时任务类型线程池
ThreadPoolTaskExecutor
基本了解,当然本文也会进行初步入门介绍
话不多说,下面是快速开始;show my code
引入
由于是写的demo版本,仅需要引入Spring
基础框架以及要求Jvm环境在1.8以上即可;
如有疑问或者错误,请大方指教,万分感谢
超时任务重试代码
/**
* @author zhangsx
* @description 简易的超时重试任务设计
* @date 2024-03-19 15:41:04
*/
public class LayZi {
public static void main(String[] args) {
ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(1);
ThreadPoolTaskExecutor taskExecutor = createExecutor();
taskExecutor.initialize();
//超时时间2s
long timeout = 2000L;
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
System.out.println("===============业务代码开始执行=================");
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//=========================模拟超时异常==========
int a = 1 / 0;
return "业务代码执行完了";
}, taskExecutor);
completableFuture.applyToEither(timeOut(threadPoolExecutor, timeout, TimeUnit.MILLISECONDS), Function.identity())
.exceptionally(throwable -> {
//TODO 此处可以对超时特殊异常类型进行判断处理
System.out.println("发生异常: " + throwable.getMessage());
System.out.println("在这里重新执行业务代码吧哈哈哈哈");
return "超时了啊";
});
String s = null;
try {
s = completableFuture.get();
} catch (Exception e) {
System.out.println("超时异常:" + e.getMessage());
}
System.out.println(s);
}
public static <T> CompletableFuture<T> timeOut(ScheduledThreadPoolExecutor threadPoolExecutor, long timeout, TimeUnit unit) {
CompletableFuture<T> result = new CompletableFuture<T>();
threadPoolExecutor.schedule(
() -> {
System.out.println("========计时任务来同步计时一下==========");
result.completeExceptionally(new Exception("超时了啊啊啊啊啊"));
},
timeout,
unit);
return result;
}
public static ThreadPoolTaskExecutor createExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(5);
// 设置最大线程数
executor.setMaxPoolSize(8);
// 设置队列容量
executor.setQueueCapacity(3);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(5);
// 设置默认线程名称
executor.setThreadNamePrefix("test-");
// 设置拒绝策略: 线程池满后阻断请求
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;
}
}
实现思路
-
使用
CompletableFuture
中applyToEither()
方法来对两个任务进行聚合,参考方法注释-
applyToEither()
:两个任务哪个执行的快,就使用哪一个结果,有返回值 -
acceptEither()
:两个任务哪个执行的快,就消费哪一个结果,无返回值 -
runAfterEither()
:任意一个任务执行完成,进行下一步操作(Runnable类型任务)
-
-
ScheduledThreadPoolExecutor
带有定时器的线程池执行器,它继承了ThreadPoolExecutor
,底层使用了延迟队列来对任务进行定时处理,如下是基本的常用方法-
schedule(Runnable command, long delay, TimeUnit unit)
// 无返回值的延迟任务 -
schedule(Callable callable, long delay, TimeUnit unit)
// 有返回值的延迟任务 -
scheduleAtFixedRate(Runnable command, long initialDelay, long period,TimeUnit unit)
// 固定频率周期任务 -
scheduleWithFixedDelay(Runnable command,long initialDelay, long delay, TimeUnit unit)
// 固定延迟周期任务
本次使用了基本的无返回值延迟任务,也就是空的任务执行,超时后使用完成任务异常方法来抛出指定异常,这样
applyToEither
来组合原本的业务时,可以对超时特殊异常进行二次执行处理。如果没有超时,则最后使用future来get获取业务执行的结果。 -
后续优化
优化方向
-
将
ScheduledThreadPoolExecutor
定时任务线程池设置为后台守护线程,项目可以进行公用 -
尝试对
exceptionally
进行判断处理,增加重试次数参数,满足多次任务重试
其他解释
CompletableFuture
中指定线程池参数,也就是demo中的taskExecutor
参数的目的是自定义线程池,避免了使用CompletableFuture
实现中默认的线程池**ForkJoinPool.commonPool()
**,使用默认的缺点就是,全局唯一,业务公用,不好维护,也容易相互影响,强烈推荐使用自定义线程池;