定时任务实现的几种方式:
1、JDK自带
- (1)Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。
- (2)ScheduledExecutorService:也jdk自带的一个类;是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。
2、Spring Task
- Spring Task :Spring3.0以后自带的task,Spring Boot提供的用于定时任务控制的注解**@Scheduled注解**,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。
3、第三方任务调度框架
除了使用Spring框架提供的 @Scheduled 注解和SchedulingConfigurer接口外,还有许多第三方的任务调度库可供选择。这些库通常提供了更多的功能和灵活性,以满足各种复杂的任务调度需求。以下是一些常见的第三方任务调度库:
- Quartz Scheduler:是一个功能强大且灵活的任务调度库,具有丰富的功能,如支持基于cron表达式的任务调度、集群支持、作业持久化等。它可以与Spring框架集成,并且被广泛应用于各种类型的任务调度应用程序中。
- xxl-job:是一个分布式任务调度平台,提供了可视化的任务管理界面和多种任务调度方式,如单机任务、分布式任务、定时任务等。它支持任务执行日志、任务失败重试、动态调整任务执行策略等功能。
- Elastic Job:是一个分布式任务调度框架,可以轻松实现分布式任务调度和作业执行。它提供了分布式任务执行、作业依赖关系、作业分片等功能,适用于大规模的分布式任务调度场景。
- PowerJob:是一个开源的分布式任务调度框架,由阿里巴巴集团开发并开源。PowerJob 提供了分布式、高可用的任务调度能力,支持多种任务类型,如定时任务、延时任务、流程任务等。
目录
[二、Spring Task](#二、Spring Task)
一、JDK自带
1、使用Timer
java
/**
* Timer:这是java自带的java.util.Timer类
*
* 这个类允许你调度一个java.util.TimerTask任务
* 使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行
* 【一般用的较少】
*/
@Configuration
public class JDKTimerTask {
@Bean
public void test(){
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.err.println("task run:"+ LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
}
};
Timer timer = new Timer();
//安排指定的任务在指定的时间开始进行重复的固定延迟执行。这里是每3秒执行一次
timer.schedule(timerTask,10,3000);
}
}
执行结果
2、使用ScheduledExecutorService
java
/**
* ScheduledExecutorService:也jdk自带的一个类;
* 是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行
* 也就是说,任务是并发执行,互不影响。
*/
@Configuration
public class ScheduledExecutorServiceTimerTask {
@Bean
public void test(){
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
// 参数:1、任务体 2、首次执行的延时时间
// 3、任务执行间隔 4、间隔时间单位
service.scheduleAtFixedRate(()->System.out.println("task ScheduledExecutorService "+ LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName()), 0, 3, TimeUnit.SECONDS);
}
}
执行结果:
二、Spring Task
@Scheduled注解是Spring Boot提供的用于定时任务控制的注解,主要用于控制任务在某个指定时间执行,或者每隔一段时间执行
@Scheduled需要配合**@EnableScheduling**使用。使用时,将@Scheduled注解放在待定时的方法名上方,将 @EnableScheduling放在项目主启动类类名上方。@Scheduled主要有三种配置执行时间的方式:cron 、fixedRate 和 fixedDelay。
注意:@Scheduled是不支持年份设置,spring quartz支持
1、基础使用案例
(1)主启动类增加注解**@EnableScheduling**:表明开启定时任务
java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling //开启定时任务注解
public class TimedTaskDemoApplication {
public static void main(String[] args) {
SpringApplication.run(TimedTaskDemoApplication.class, args);
}
}
(2)定时任务执行方法上增加注解**@Scheduled**:表明该方法为定时任务需要执行的内容
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
@Slf4j
public class TaskDemo {
// @Scheduled(cron = "0 0 4 ? * SAT")//每周六凌晨4:00
@Scheduled(cron = "* * * * * *")//每秒执行一次
private void doTask(){
log.info("执行定时任务:" + new Date());
}
}
2、参数说明简介
| 参数 | 说明 | |
| cron | 任务执行的cron表达式 | |
| zone | cron表达时解析使用的时区,默认为服务器的本地时区。 使用java.util.TimeZone#getTimeZone(String)方法解析 | GMT-8:00 |
| fixedRate | 固定速率 上一次任务执行开始到下一次执行开始的间隔时间固定,单位为ms。 若在调度任务执行时,上一次任务还未执行完毕,会加入worker队列,等待上一次执行完成后,马上执行下一次任务 | 1000 |
| fixedRateString | 与fixedRate一致,只是间隔时间使用java.time.Duration#parse解析 | 1000或PT1S |
| fixedDelay | 固定延迟 上一次任务执行结束到下一次执行开始的间隔时间固定,单位为ms。 | 1000 |
| fixedDelayString | 与fixedDelay一致,只是间隔时间使用java.time.Duration#parse解析 | 1000或PT1S |
| initialDelay | 首次延迟多长时间后执行,单位ms。 之后按照fixedRate、fixedRateString、fixedDelay、fixedDelayString指定的规则执行,需要指定其中一个规则。 注意:不能和cron一起使用 | 1000 |
initialDelayString | 与initialDelay 一致,只是间隔时间使用java.time.Duration#parse解析 | 1000或PT1S |
---|
关于 fixedRate 和 fixedDelay的区别:
fixedRate 的间隔时间是上次任务开始后,开始计算时间间隔,达到指定时间间隔后,开始执行下一次任务
fixedDelay 的间隔时间是上次任务结束后,开始计算时间间隔,达到指定时间间隔后,开始执行下一次任务
例如:当一个任务需要统计大量数据,并根据不用用户生成不同文件报表,并将生成的报表推送到相应的用户下,该任务有补充机制。在执行该任务时,将使用大量时间时,此时使用 fixedRate 就会造成工作队列长时间堆积,同时,如果使用 fixedRate 的话,与 cron 的作用高度重合,基本可以用 cron 表达式替代,这个情况下,使用 fixedDelay 效果更好,这是在一个任务结束后,再次执行该任务进行重试,直到所有用户都拿到相应的文件。
3、cron表达式
cron
是@Scheduled
的一个参数,是一个字符串,以空格隔开
@Scheduled(cron = "{秒数} {分钟} {小时} {日期} {月份} {星期}")
(1)cron参数配置描述
| 单位 | 允许值 | 允许通配符 |
| 秒 | 0-59 | , - * / |
| 分钟 | 0-59 | , - * / |
| 小时 | 0-23 | , - * / |
| 日期 | 1-31 | , - * / ? L W |
| 月份 | 1-12或JAN-DEC(大小写均可) | , - * / ? |
星期 | 1-7或SUN-SAT(大小写均可) 注:星期日为每周第一天,所以1-7表示周末到周六 | , - * / ? L # |
---|
(2)cron通配符描述
| 符号 | 含义 |
| * | 所有值,在秒字段上表示每秒执行,在月字段上表示每月执行 |
| ? | 不指定值,不需要关心当前指定的字段的值,比如每天都执行但不需要关心周几就可以把周的字段设为? |
| - | 区间或者范围,如秒的0-2 ,表示0秒、1秒、2秒都会触发 |
| , | 指定值,比如在0秒、20秒、25秒触发,可以把秒的字段设为0,20,25 |
| / | 递增触发,比如秒的字段上设0/3 ,表示从第0秒开始,每隔3秒触发 |
| L | 最后,只允许在日字段或周字段上,在日字段上使用L表示当月最后一天,在周字段上使用3L表示该月最后一个周二 |
| W | 只允许用在日字段上,表示距离最近的该日的工作日,工作日指的是周一至周五 |
# | 只允许在周字段上,表示每月的第几个周几,如2#3 , 每月的第3个周二 |
---|
说明:
在子表达式(分钟)里的"0/15"表示从第0分钟开始,每15分钟
在子表达式(分钟)里的"3/20"表示从第3分钟开始,每20分钟(它和"3,23,43")的含义一样
"?"字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为"?"
"L" 字符仅被用于天(月)和天(星期)两个子表达式,它是单词"last"的缩写但是它在两个子表达式里的含义是不同的。
在天(月)子表达式中,"L"表示一个月的最后一天
在天(星期)自表达式中,"L"表示一个星期的最后一天,也就是SAT
如果在"L"前有具体的内容,它就具有其他的含义了
例如:"6L"表示这个月的倒数第6天,"FRIL"表示这个月的最一个星期五
注意:在使用"L"参数时,不要指定列表或范围,因为这会导致问题
4、cron案例
"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 ? * 6#3" 每月的第三个星期五上午10:15触发
5、实现多任务并行(默认单线程)
默认情况下如果存在多个定时任务方法是单线程同步执行
验证代码:创建两个定时任务,其中一个睡眠3秒钟
java
@Component
@Slf4j
public class TaskDemo {
@Scheduled(cron = "* * * * * *")//每秒执行一次
private void doTask(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("执行定时任务1:" + new Date());
}
@Scheduled(cron = "* * * * * *")//每秒执行一次
private void doTask2(){
log.info("执行定时任务【2】:" + new Date());
}
}
单线程验证结果:从执行时间和线程名可以看出两个定时任务是单线程,使用的是同一个线程执行的
增加配置类:异步任务配置类进行线程池的设置
java
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executor;
/**
* 异步任务配置类
* 需要实现SchedulingConfigurer接口
* 需要实现AsyncConfigurer接口
*/
@Configuration
public class AsynTaskConfig implements AsyncConfigurer,SchedulingConfigurer {
// 定义池子的容量
private static final int CRON_POOL_SIZE = 10;
// 注册定时任务线程池
@Bean
public ThreadPoolTaskScheduler getThreadPoolTaskScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
// 初始化线程池
threadPoolTaskScheduler.initialize();
// 设置池子容量
threadPoolTaskScheduler.setPoolSize(CRON_POOL_SIZE);
return threadPoolTaskScheduler;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setTaskScheduler(getThreadPoolTaskScheduler());
}
//重写AsyncConfigurer的两个方法(异步的配置)
@Override
public Executor getAsyncExecutor() {
return AsyncConfigurer.super.getAsyncExecutor();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler();
}
}
异步多线程验证结果:从执行时间和线程名可以看出两个定时任务是多线程
6、局限性(不支持年份设定)
@Scheduled 的 cron 无法指定执行的年份
spring taks 不支持年位定时,它毕竟不是quartz,只是简单的定时框架,比起jdk Timer就加入了线程池而以.
一旦制定到年份,会存在问题,启动项目的时候,会一直报一个错误,大概的意思是你的定时任务将永远不会被执行,导致项目一直启动不了。
错误场景一:年份设置在第7位时报错,cron只支持到六位
错误场景二:年份设置在第6位时报错,cron第六位表示星期,内容范围只支持1-7