任务调度
java实现定时任务
定时任务种类:
-
Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行;而且作业类需要集成java.util.TimerTask,一般用的较少。
-
Spring3.0以后自带的task,即:spring schedule,可以将它看成一个轻量级的 Quartz ,而且使用起来比Quartz简单许多。
-
Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行;代码稍显复杂。
-
xxl-job
https://blog.csdn.net/weixin_57109001/article/details/127301536?spm=1001.2014.3001.5502
一,jdk自带的Timer类
这是JDK自带的定时任务执行类,所以操作简单,使用方便,具体使用如下:
public class MyTime {
public static void main(String[] args) {
//自定义一个任务
TimerTask task = new TimerTask() {
@Override
public void run() {
//要执行的任务内容
System.out.println("定时任务:" + new Date());
}
};
Timer timer = new Timer();
//延迟1S执行,每3S执行一次
timer.schedule(task, 1000, 3000);
}
}
执行内容如下:
定时任务:Thu Oct 13 14:53:41 CST 2022
定时任务:Thu Oct 13 14:53:44 CST 2022
定时任务:Thu Oct 13 14:53:47 CST 2022
定时任务:Thu Oct 13 14:53:50 CST 2022
定时任务:Thu Oct 13 14:53:53 CST 2022
定时任务:Thu Oct 13 14:53:56 CST 2022
- 由于执行任务的线程只有一个,所以如果某个任务的执行时间过长,那么将破坏其他任务的定时精确性。如一个任务每1秒执行一次,而另一个任务执行一次需要5秒,那么如果是固定速率的任务,那么会在5秒这个任务执行完成后连续执行5次,而固定延迟的任务将丢失4次执行。
- 如果执行某个任务过程中抛出了异常,那么执行线程将会终止,导致Timer中的其他任务也不能再执行。
- Timer使用的是绝对时间,即是某个时间点,所以它执行依赖系统的时间,如果系统时间修改了的话,将导致任务可能不会被执行。
由于Timer存在上面说的这些缺陷,在JDK1.5中,我们可以使用ScheduledThreadPoolExecutor来代替它,使用Executors.newScheduledThreadPool工厂方法或使用ScheduledThreadPoolExecutor的构造函数来创建定时任务,它是基于线程池的实现,不会存在Timer存在的上述问题,当线程数量为1时,它相当于Timer。
二,JDK1.5自带的ScheduledExecutorService
public class SESTask {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println("进入task" + new Date());
}, 1, 3, TimeUnit.SECONDS);
}
}
执行结果如下:
进入taskThu Oct 13 14:59:10 CST 2022
进入taskThu Oct 13 14:59:13 CST 2022
进入taskThu Oct 13 14:59:16 CST 2022
进入taskThu Oct 13 14:59:19 CST 2022
三,Spring Task
Spring Task 底层是基于 JDK 的 ScheduledThreadPoolExecutor 线程池来实现的。
只需要两步即可实现:
1.开启定时任务只需要在 Spring Boot 的启动类上声明 @EnableScheduling 即可,实现代码如下:
@SpringBootApplication
@EnableScheduling // 开启定时任务
public class DemoApplication {
}
2.添加定时任务
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component // 把此类托管给 Spring,不能省略
public class TaskUtils {
// 添加定时任务
@Scheduled(cron = "59 59 23 0 0 5") // cron 表达式,每周五 23:59:59 执行
public void doTask(){
System.out.println("定时任务~");
}
}
Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式:
Seconds Minutes Hours DayofMonth Month DayofWeek Year或
Seconds Minutes Hours DayofMonth Month DayofWeek
每一个域可出现的字符如下:
Seconds:可出现", - * /"四个字符,有效范围为0-59的整数
Minutes:可出现", - * /"四个字符,有效范围为0-59的整数
Hours:可出现", - * /"四个字符,有效范围为0-23的整数
DayofMonth:可出现", - * / ? L W C"八个字符,有效范围为0-31的整数
Month:可出现", - * /"四个字符,有效范围为1-12的整数或JAN-DEc
DayofWeek:可出现", - * / ? L C #"八个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推
Year:可出现", - * /"四个字符,有效范围为1970-2099年
每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:
(1):表示匹配该域的任意值,假如在Minutes域使用, 即表示每分钟都会触发事件。
(2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用,如果使用表示不管星期几都会触发,实际上并不是这样。
(3)-:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
(4)/:表示数值的增量,简单来说,比如分写上0/5表示从0分开始,每隔5分钟
(5),:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在第5和第20分钟分别触发一次。
(6)L:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
(7)W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份
(8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个非周六周末的日期。
(9)#:用于确定每个月第几个星期几,只能出现在DayofWeek域。例如在4#2,表示某月的第二个星期三。
举几个例子:
0 0 2 1 * ? * 表示在每月的1日的凌晨2点调度任务
0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
0 15 10 ? * 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12点
"0 0 12 * * ?" 每天中午12点触发
"0 15 10 ? * *" 每天上午10:15触发
"0 15 10 * * ?" 每天上午10:15触发
"0 15 10 * * ? *" 每天上午10:15触发
"0 15 10 * * ? 2005" 2005年的每天上午10:15触发
"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发
"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发
"0 15 10 15 * ?" 每月15日上午10:15触发
"0 15 10 L * ?" 每月最后一日的上午10:15触发
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发
四,Quartz
Quartz 主要是基于 Java 的任务调度框架,支持 Java 语言
什么是Quartz?
一个定时任务调度框架,简单易用,功能强大可以使实现定时任务的。 在项目开发过程当中,某些定时任务,可能在运行一段时间之后,就不需要了,或者需要修改下定时任务的执行时间等等。 需要在代码当中进行修改然后重新打包发布,很麻烦。使用Quartz来实现的话不需要重新修改代码而达到要求。
springboot整合Quartz定时调度框架
开发环境
- JDK版本1.8
- springboot版本:2.1.0
- 开发工具:IDEA
实现一个简单的定时任务
第一步 引入对应的jar
在springboot2.0后官方添加了Quartz框架的依赖,所以只需要在pom文件当中引入
<!--引入quartz定时框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
第二步 创建一个定时任务
由于springboot2.0自动进行了依赖所以创建的定时任务类直接继承QuzrtzJobBean就可以了,新建一个定时任务类:MyTask
public class MyTask extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//TODO 这里写定时任务的执行逻辑
System.out.println("简单的定时任务执行时间:"+new Date().toLocaleString());
}
}
第三步 创建Quartz配置类将之前创建的定时任务添加到定时调度里面
@Configuration
public class QuartzConfig {
//指定具体的定时任务类
@Bean
public JobDetail uploadTaskDetail() {
return JobBuilder.newJob(MyTask.class).withIdentity("MyTask").storeDurably().build();
}
@Bean
public Trigger uploadTaskTrigger() {
//TODO 这里设定执行方式
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("*/5 * * * * ?");
// 返回任务触发器
return TriggerBuilder.newTrigger().forJob(uploadTaskDetail())
.withIdentity("MyTask")
.withSchedule(scheduleBuilder)
.build();
}
}
最后运行项目查看效果
成功执行任务
这种方式是写死在程序当中的,也存在修改不方便的情况!!!!!!
实现定时调度任务的动态暂停,修改,启动,单次执行等操作
第一步 创建一个定时任务相关实体类用于保存定时任务相关信息到数据库当中
public class QuartzBean {
/** 任务id */
private String id;
/** 任务名称 */
private String jobName;
/** 任务执行类 */
private String jobClass;
/** 任务状态 启动还是暂停*/
private Integer status;
/** 任务运行时间表达式 */
private String cronExpression;
//省略getter setter
}
第二步 创建定时任务暂停,修改,启动,单次启动工具类
public class QuartzUtils {
/**
* 创建定时任务 定时任务创建之后默认启动状态
* @param scheduler 调度器
* @param quartzBean 定时任务信息类
* @throws Exception
*/
public static void createScheduleJob(Scheduler scheduler, QuartzBean quartzBean){
try {
//获取到定时任务的执行类 必须是类的绝对路径名称
//定时任务类需要是job类的具体实现 QuartzJobBean是job的抽象类。
Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(quartzBean.getJobClass());
// 构建定时任务信息
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(quartzBean.getJobName()).build();
// 设置定时任务执行方式
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression());
// 构建触发器trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(quartzBean.getJobName()).withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, trigger);
} catch (ClassNotFoundException e) {
System.out.println("定时任务类路径出错:请输入类的绝对路径");
} catch (SchedulerException e) {
System.out.println("创建定时任务出错:"+e.getMessage());
}
}
/**
* 根据任务名称暂停定时任务
* @param scheduler 调度器
* @param jobName 定时任务名称
* @throws SchedulerException
*/
public static void pauseScheduleJob(Scheduler scheduler, String jobName){
JobKey jobKey = JobKey.jobKey(jobName);
try {
scheduler.pauseJob(jobKey);
} catch (SchedulerException e) {
System.out.println("暂停定时任务出错:"+e.getMessage());
}
}
/**
* 根据任务名称恢复定时任务
* @param scheduler 调度器
* @param jobName 定时任务名称
* @throws SchedulerException
*/
public static void resumeScheduleJob(Scheduler scheduler, String jobName) {
JobKey jobKey = JobKey.jobKey(jobName);
try {
scheduler.resumeJob(jobKey);
} catch (SchedulerException e) {
System.out.println("启动定时任务出错:"+e.getMessage());
}
}
/**
* 根据任务名称立即运行一次定时任务
* @param scheduler 调度器
* @param jobName 定时任务名称
* @throws SchedulerException
*/
public static void runOnce(Scheduler scheduler, String jobName){
JobKey jobKey = JobKey.jobKey(jobName);
try {
scheduler.triggerJob(jobKey);
} catch (SchedulerException e) {
System.out.println("运行定时任务出错:"+e.getMessage());
}
}
/**
* 更新定时任务
* @param scheduler 调度器
* @param quartzBean 定时任务信息类
* @throws SchedulerException
*/
public static void updateScheduleJob(Scheduler scheduler, QuartzBean quartzBean) {
try {
//获取到对应任务的触发器
TriggerKey triggerKey = TriggerKey.triggerKey(quartzBean.getJobName());
//设置定时任务执行方式
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression());
//重新构建任务的触发器trigger
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//重置对应的job
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
System.out.println("更新定时任务出错:"+e.getMessage());
}
}
/**
* 根据定时任务名称从调度器当中删除定时任务
* @param scheduler 调度器
* @param jobName 定时任务名称
* @throws SchedulerException
*/
public static void deleteScheduleJob(Scheduler scheduler, String jobName) {
JobKey jobKey = JobKey.jobKey(jobName);
try {
scheduler.deleteJob(jobKey);
} catch (SchedulerException e) {
System.out.println("删除定时任务出错:"+e.getMessage());
}
}
}
第三步 创建一个定时任务和相关测试类。
新建一个定时任务MyTask1
public class MyTask1 extends QuartzJobBean {
//验证是否成功可以注入service 之前在ssm当中需要额外进行配置
// @Autowired
// private AccountService service;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// Account account = new Account();
// account.setId(1);
// account = service.findByAccount(account);
// System.out.println(account.toString());
//TODO 这里写定时任务的执行逻辑
System.out.println("动态的定时任务执行时间:"+new Date().toLocaleString());
}
}
新建一个测试Controller
@Controller
@RequestMapping("/quartz/")
public class QuartzController {
//注入任务调度
@Autowired
private Scheduler scheduler;
@RequestMapping("/createJob")
@ResponseBody
public String createJob(QuartzBean quartzBean) {
try {
//进行测试所以写死
quartzBean.setJobClass("com.hjljy.blog.Quartz.MyTask1");
quartzBean.setJobName("test1");
quartzBean.setCronExpression("*/10 * * * * ?");
QuartzUtils.createScheduleJob(scheduler,quartzBean);
} catch (Exception e) {
return "创建失败";
}
return "创建成功";
}
@RequestMapping("/pauseJob")
@ResponseBody
public String pauseJob() {
try {
QuartzUtils.pauseScheduleJob (scheduler,"test1");
} catch (Exception e) {
return "暂停失败";
}
return "暂停成功";
}
@RequestMapping("/runOnce")
@ResponseBody
public String runOnce() {
try {
QuartzUtils.runOnce (scheduler,"test1");
} catch (Exception e) {
return "运行一次失败";
}
return "运行一次成功";
}
@RequestMapping("/resume")
@ResponseBody
public String resume() {
try {
QuartzUtils.resumeScheduleJob(scheduler,"test1");
} catch (Exception e) {
return "启动失败";
}
return "启动成功";
}
@RequestMapping("/update")
@ResponseBody
public String update(QuartzBean quartzBean) {
try {
//进行测试所以写死
quartzBean.setJobClass("com.hjljy.blog.Quartz.MyTask1");
quartzBean.setJobName("test1");
quartzBean.setCronExpression("10 * * * * ?");
QuartzUtils.updateScheduleJob(scheduler,quartzBean);
} catch (Exception e) {
return "启动失败";
}
return "启动成功";
}
}
然后在网页上输入对应URL进行暂停,启动,创建,修改,单次运行等操作就可以了。
第四步 总结
1 springboot2.0后默认添加了quartz的依赖,可以少些很多配置信息,只需要写好自己的任务类(需要实现job类)然后通过调度器scheduler添加任务就可以了。
2 通过@Bean注解简单创建定时任务的时候,直接写任务类的class就可以,但是通过scheduler的时候需要写绝对名称。
3 在quartz任务暂停之后再次启动时,会立即执行一次,在更新之后也会立即执行一次。
4 在springboot当中默认quartz线程池大小为10。
5 在启动项目初始化时需要将项目的定时任务也进行初始化。这样比较方便不用依次进行启动
五,xxl-job
https://blog.csdn.net/qq_16855077/article/details/111047768?utm_medium=distribute.wap_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-111047768-blog-80864406.237%5Ev3%5Ewap_relevant_t0_download&spm=1001.2101.3001.4242.1
万字长文简单明了的介绍xxl-job以及quartz
XXL-Job 和 Quartz 区别
XXL-Job 和 Quartz 都是 Java 项目中常用的定时任务框架,它们有以下几点区别:
xxl-job 和 Quartz 都是用于任务调度的开源框架,它们之间有一些区别,主要体现在以下几个方面:
语言支持:
Quartz 主要是基于 Java 的任务调度框架,支持 Java 语言。
xxl-job 是一个分布式任务调度平台,它提供了 Java 版本的调度中心,同时还提供了 Python、PHP 等语言的任务执行器,因此支持多种语言。
分布式支持:
Quartz 本身并不提供原生的分布式支持,需要通过一些扩展或者和其他组件结合来实现分布式任务调度。
xxl-job 专注于分布式任务调度,提供了分布式任务调度的解决方案,可以在多个节点上进行任务调度和执行。
管理界面和监控功能:
Quartz 并没有提供官方的任务调度管理界面和监控功能,通常需要结合其他组件或者自行开发管理界面。
xxl-job 提供了任务调度中心,包括任务管理、调度监控、日志查看等功能,方便管理和监控任务的执行情况。
社区活跃度:
Quartz 是一个非常成熟和稳定的任务调度框架,拥有庞大的用户社区和丰富的生态系统。
xxl-job 相对较新,但也有着活跃的社区和持续的更新支持。
总的来说,Quartz 是一个强大的、成熟的任务调度框架,主要支持 Java 语言,而 xxl-job 则是一个专注于分布式任务调度的平台,支持多种语言,并提供了任务调度中心和监控功能。选择使用哪个框架取决于具体的需求和场景。