通过上篇文章:SpringBoot专栏:集成定时ScheduledTasks任务(第14讲)的简单讲解,我们都看到了SpringBoot做了很多自动配置,使集成变得异常简单。
然则我们不应该停留在基本使用上,所有的技术都是要支撑业务的,所以我们应该会想到如下问题(想到的同学加薪)
1)定时任务会在什么业务场景上使用?
2)定时任务如果像上篇在代码中配置(java类),定时时间时间更改了怎么办,是否需要重启服务
3)定时会存在并发问题吗?
...
定时任务设置会遇到页面化的操作,也就意味着我们需要动态的设置定时任务 停止、运行、重启;在执行定时任务的同时稍微考虑下并发遇到的场景;在运用定时任务定时任务fixedRate 和fixedDelay选择合适的场景使用。
基于上面问题,特地编写了一个demo样例,帮我们解决动态设置定时任务,详解fixedRate 和fixedDelay,插入并发编程概念(主要希望大家了解下该技能点)
定时任务业务场景
1. 比如周期性更新数据库
2.定时跑一些评分、统计数据等
3.延迟执行..
不管什么场景,只要我们掌握了核心编程技术,所以都能get的思路。
好了样例开始,不明白的可以私信或者留言 @架构师速成记
启动类(如上篇文章所述)
新增 ScheduledTasks类
@Component
public class ScheduledTasks {
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
//可以用原子方式更新的 boolean 值
private AtomicBoolean firstTime = new AtomicBoolean(true);
//AtomicInteger这个类的存在是为了满足在高并发的情况下,原生的整形数值自增线程不安全的问题。
private AtomicInteger number = new AtomicInteger();
/**
* @Scheduled(fixedRate = 2000) :上一次开始执行时间点之后5秒再执行
* @Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行
* @Scheduled(initialDelay=1000, fixedRate=5000) :第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次
* @Scheduled(cron=" /5 ") :通过cron表达式定义规则,什么是cro表达式,自行搜索引擎。
*
* fixedRate 还有一个误区就是,以为任务时长超过 fixedRate 时会启动多个任务实例,
* 其实不会; 只不过会在上次任务执行完后立即启动下一轮。除非这个 Job 方法用
* @Async 注解了,使得任务不在 TaskScheduler 线程池中执行,而是每次创建新线程来执行
* 通过下面的这个列子我们可以看到在第一休眠7秒之后 ,以后的几个定时任务同时执行
* fixedDelay 的逻辑就相当简单:总是上次任务结束 5 秒后,fixedDelay 不存在任务的预先编排操作,是相机而为。
*/
@Scheduled(fixedRate = 2000)
public void reportCurrentTime() {
LocalTime start = LocalTime.now();
System.out.println(Thread.currentThread() + " 开始时间: " + number.incrementAndGet() + " -->" + start);
// 以原子方式设置为给定值,并返回以前的值
if (firstTime.getAndSet(false)) {
try {
//休眠7秒
Thread.sleep(7000);
} catch (InterruptedException e) {
}
}
LocalTime end = LocalTime.now();
System.out.println(Thread.currentThread() + " 运行结束: " + number.get() + " @ " + end
+ ", 任务用时 " + (ChronoUnit.SECONDS.between(start, end)));
}
}
Controller类
@RestController
@Component //不要忘记!不要忘记!
public class TaskController {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
private String cronStr = "*/5 * * * * *";
/**
* 首先这里我们需要重新认识一个类ThreadPoolTaskScheduler:线程池任务调度类,能够开启线程池进行任务调度。
*
* ThreadPoolTaskScheduler.schedule()方法会创建一个定时计划ScheduledFuture,在这个方法需要添加两个参数,
* Runnable(线程接口类) 和CronTrigger(定时任务触发器)
*/
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
/**
* ScheduledFuture中有一个cancel可以停止定时任务
*/
private ScheduledFuture> future;
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
return new ThreadPoolTaskScheduler();
}
/**
* 开始一个定时任务
* @return
*/
@RequestMapping("/start")
public String startCron() {
future = threadPoolTaskScheduler.schedule(new MyRunnable(), new CronTrigger("0/5 * * * * *"));
System.out.println("DynamicTask.startCron()");
return "startCron";
}
/**
* 关闭定时任务
* @return
*/
@RequestMapping("/stop")
public String stopCron() {
if (future != null) {
future.cancel(true);
}
System.out.println("动态关闭定时任务");
return "success";
}
/**
* 重启定时任务
* @return
*/
@RequestMapping("/restart")
public String restart() {
stopCron();// 先停止,在开启.
future = threadPoolTaskScheduler.schedule(new MyRunnable(), new CronTrigger("*/10 * * * * *"));
System.out.println("动态重启");
return "动态改定时任务";
}
@GetMapping(value = "start1")
public String startTask(){
System.out.println("startCron1 >>>>");
threadPoolTaskScheduler.schedule(new MyRunnable(), new Trigger(){
@Override
public Date nextExecutionTime(TriggerContext triggerContext){
System.out.println("开始运行"+new Date());
return new CronTrigger(cronStr).nextExecutionTime(triggerContext);
}
});
System.out.println("startCron1 <<<<");
return "success";
}
/**
* run方法
*/
private class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("run方法运行," + dateFormat.format(new Date()));
}
}
}
ScheduledTasks类中有个定时方法,启动后如下
分析:
1)fixedRate 和fixedDelay两者区别
通过下面的这个列子我们可以看到在第一休眠7秒之后 ,以后的几个定时任务同时执行
fixedDelay 的逻辑就相当简单:总是上次任务结束 5 秒后,fixedDelay 不存在任务的预先编排操作,是相机而为。
以上也是fixedRate 和fixedDelay两者的区别。
2)并发语法
细心的同学可以看到我们用到了AtomicBoolean、AtomicInteger等
AtomicBoolean:可以用原子方式更新的 boolean 值
firstTime.getAndSet(false): 以原子方式设置为给定值,并返回以前的值
AtomicInteger:AtomicInteger这个类的存在是为了满足在高并发的情况下,原生的整形数值自增线程不安全的问题
主要稍微带了并发编程遇到的基本语法,大家可以留意下
3)动态设置定时任务参考controller
启动定时任务
停止定时任务
日志如下:可以看到定时任务可以控制启动和停止(其它的重启以及其他的操作可以参考源码)
2018-12-29 21:09:21.311 INFO 30064 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 38 ms
DynamicTask.startCron()
run方法运行,2018-12-29 21:09:25
run方法运行,2018-12-29 21:09:30
run方法运行,2018-12-29 21:09:35
run方法运行,2018-12-29 21:09:40
run方法运行,2018-12-29 21:09:45
动态关闭定时任务
定时任务到这就结束了,也希望大家对定时任务有个新的认识,不断的扩展新技能点。
快元旦了,领导请我们聚餐,每人都喝了几瓶,结果回来晕乎乎的,码了2个小时才码完这篇,希望对大家有帮助。
坚持写作1个月了,现在发现朋友的一次转发、关注是对本文的最大支持,鼓励下呗,转发支持一次
源码下载(今天刚码的)
https://github.com/shinians/springboot-demos
对了还有个Cron 表达式的遗留问题,不会配置?没事,没关系,给提供下自动生成工具
更多信息可以咨询 @架构师速成记