网站首页 > java教程 正文
最近工作中遇到个问题,业务需求是要接入供应商类似漏洞扫描的设备,获取他们扫描的结果入库。由于扫描是一个漫长的过程所以并不能即时调接口拿到结果,根据供应商的提供的文档发现,提供了任务创建接口,任务进度查询接口,任务结果获取接口,最后觉得使用@Scheduled定时任务,创建任务将任务数据入库,每隔10分钟查询数据库未完成任务,调一次进度接口,进度为100时再去掉结果接口,最后结果入库,更改数据库任务状态为完成。伪代码大致如下:
1.创建任务
public void createTask() {
// 1. http 调用创建接口,获取任务编号
// 2. 任务编号,扫描中状态,入库操作
}
2.定时任务编写
@Scheduled(cron = "0/10 * * * * *")
public void timeGetTaskRes() {
// 1. 查询数据库 任务状态是扫描中的数据
// 2. http调用任务进度接口,获取进度
// 3. 判断进度是否是100,如果是100,调用结果接口获取结果操作入库,更改数据库任务状态
int process = 100;
if (process == 100) {
// 1.是100,调用结果接口获取结果操作入库
// 2.更改数据库任务状态
}
}
有的同学急了,这不就是个简单的定时任务吗?哪来的定时器嵌套?莫慌,需求这时又来了,说除了要获取任务结果的同时,还要获取这个任务的报告。查看供应商文档得知,这个报告也是同样的一套,创建,进度,获取,并且还必须等扫描任务结束才能创建,又要定时获取进度,到100才能获取模板。业务流程下图:
定时任务优化,下面给出全量代码(模拟业务):
1.定义线程池
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.concurrent.*;
/**
* 定时任务的嵌套demo
*/
@Component
public class ScheduleTaskDemoTestConfig {
/**
* 定时调度线程池
* (ScheduledThreadPoolExecutor): 可以周期性地或延迟地执行任务
*/
@Bean("scheduledExecutorService")
public ScheduledExecutorService getScheduledExecutorService() {
return new ScheduledThreadPoolExecutor(5,
new BasicThreadFactory.Builder().namingPattern("getTemplate-schedulePool-%d").daemon(true).build());
}
/**
* 固定大小线程池(FixedThreadPool)
* 线程池的大小是固定的,超出核心线程数的任务将被放在无界队列中等待执行。
*/
@Bean("fixedExecutorService")
public ExecutorService getFixedThreadPool() {
// 创建一个包含5个线程的定长线程池
return new ThreadPoolExecutor(
// 核心线程数,线程池一直维持的线程数
5,
// 最大线程数,线程池允许的最大线程数,这里设置为与核心线程数相同,表示不增加额外线程
5,
// 空闲线程存活时间,设置为0表示线程不会被回收
0L,
// 时间单位
TimeUnit.MILLISECONDS,
// 工作队列,这里是无界队列,容量为Integer.MAX_VALUE,可修改为有界队列控制任务数量
new LinkedBlockingQueue<>(100),
// 拒绝策略,当线程池和工作队列已满时,新提交的任务将由调用者线程执行
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
}
2.定时任务优化编写
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.*;
/**
* 定时任务的嵌套demo
*/
@Component
@Slf4j
public class ScheduleTaskDemoTest {
/**
* 注入定时线程池
*/
@Autowired // 使用@Qualifier指定了要注入的Bean的名称
@Qualifier("scheduledExecutorService")
private ScheduledExecutorService scheduledExecutorService;
/**
* 注入定长线程池
*/
@Autowired
@Qualifier("fixedExecutorService")
private ExecutorService fixedExecutorService;
private ScheduledFuture<?> scheduledFuture;
public static void main(String[] args) {
log.info("调用创建接口 ===> 创建扫描任务接口 ");
}
// 定时任务 60s 执行一次
@Scheduled(cron = "0/60 * * * * ? ")
public void timeGetTaskRes() {
log.info("调用扫描进度接口 ===> 获取到扫描进度 scanProcess ");
// 此处模拟进度100
int scanProcess = 100;
if (scanProcess == 100) {
// 扫描进度是100,调用结果接口获取结果操作入库
log.info("调用扫描结果接口 ===> 获取到扫描结果 ");
// 生成模板方法
handleTemplate();
log.info("已经获取报告,回到主线程 <====");
}
}
public void handleTemplate() {
log.info("扫描进度100,调用模板报告创建接口 ===> 创建模板报告接口 ");
CountDownLatch countDownLatch = new CountDownLatch(1);
scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(() -> {
// 2.http 获取模板任务进度
log.info("调用模板报告进度接口 ===> 获取到扫描进度 templateProcess ");
// 3.如果进度是100, 获取报告。 同时关闭定时器;
// 此处模拟进度100
int templateProcess = 100;
if (templateProcess == 100) {
try {
// 扫描进度是100,调用结果接口获取结果操作入库
log.info("模板报告度100,调用下载模板报告接口 ===> 获取到下载模板报告 ");
} catch (Exception e) {
log.error("调用下载模板报告接口 **** 下载模板报告出错! ");
throw new RuntimeException(e);
} finally {
log.info("已调用下载获取模板接口,关闭当前定时器");
countDownLatch.countDown();
scheduledFuture.cancel(true);
}
}
// 创建延迟3秒后开始执行,每隔15s在执行
}, 3, 15, TimeUnit.SECONDS);
fixedExecutorService.submit(()->{
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if(!scheduledFuture.isCancelled()) {
log.info("调用下载接口超时!关闭当前定时器");
countDownLatch.countDown();
scheduledFuture.cancel(true);
}
}
});
try {
countDownLatch.await();
log.info("报告获取阻塞等待退出");
} catch (InterruptedException e) {
log.info("报告获取阻塞等待停止 出错");
throw new RuntimeException(e);
}
}
}
注意点:
1.上述代码使用了CountDownLatch,由于后续代码需要用到下载的模板报告,因此此处阻塞等待模板下载完毕主线程再继续向下执行;
2.另外又单开了一个普通线程用来执行超时操作,若某个任务模板一直扫描进度很久都未完成,规定一个超时时间,到达超时时间直接抛弃这个模板下载任务;
3.scheduledFuture需要提前声明变量,不然在提交的定时任务内不能使用这个变量;
4.scheduledFuture.cancel(true)使用了这个api,没有使用shutdown()是由于后面还有其他任务会提交到定时任务线程池,shutdown就直接关闭了,无法再提交新的定时任务,因此使用了cancel;
5.再超时线程中使用了isCancled判断是否已经取消,是因为任务还未超时就已经下载好,避免重复去取消当前定时器产生冲突;
6.启动类保证要有@EnableScheduling注解,否则spring定时不会执行;
最后说明,上述定时器间隔,线程池大小,以及超时时间,大家可以根据自己的实际业务进行适当调整,希望能给大家带来启发,有问题也可以在评论区讨论,看到都会回复,感谢观看。
- 上一篇: Java中分布式定时器实现和原理介绍
- 下一篇: 100个Java工具类之67:定时执行Timer
猜你喜欢
- 2024-09-09 Java 定时器、加密、File类(java定时器时间格式)
- 2024-09-09 100个Java工具类之67:定时执行Timer
- 2024-09-09 Java中分布式定时器实现和原理介绍
- 2024-09-09 Java定时任务——SpringTask(java定时任务的实现方式)
- 2024-09-09 全面了解Java Timer定时器类(java定时器怎么用)
- 2024-09-09 Java多线程19:定时器Timer(java多线程实现方式)
- 2024-09-09 Java中的定时器:java.util.Timer(java中的定时器有哪些)
- 2024-09-09 Java 中如何实现定时任务(java定时任务底层原理)
- 2024-09-09 Java定时任务的五种创建方式,你都会么?
- 2024-09-09 Java中实现定时任务的几种方式(java中实现定时任务的几种方式有哪些)
你 发表评论:
欢迎- 最近发表
-
- Java常量定义防暴指南:从"杀马特"到"高富帅"的华丽转身
- Java接口设计原则与实践:优雅编程的艺术
- java 包管理、访问修饰符、static/final关键字
- Java工程师的代码规范与最佳实践:优雅代码的艺术
- 编写一个java程序(编写一个Java程序计算并输出1到n的阶乘)
- Mycat的搭建以及配置与启动(mycat部署)
- Weblogic 安装 -“不是有效的 JDK Java 主目录”解决办法
- SpringBoot打包部署解析:jar包的生成和结构
- 《Servlet》第05节:创建第一个Servlet程序(HelloSevlet)
- 你认为最简单的单例模式,东西还挺多
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)