定时任务实现的几种方式: 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 提供了分布式、高可用的任务调度能力,支持多种任务类型,如定时任务、延时任务、流程任务等。
目录 一、JDK自带 1、使用Timer 2、使用ScheduledExecutorService 二、Spring Task 1、基础使用案例 2、参数说明简介 3、cron表达式 (1)cron参数配置描述 (2)cron通配符描述 4、cron案例 5、实现多任务并行(默认单线程) 6、局限性(不支持年份设定) 一、JDK自带 1、使用Timer
/**
* 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
/**
* 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 :表明开启定时任务
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
:表明该方法为定时任务需要执行的内容
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表达式zonecron表达时解析使用的时区,默认为服务器的本地时区。
使用java.util.TimeZone#getTimeZone(String)方法解析GMT-8:00fixedRate固定速率上一次任务执行开始到下一次执行开始的间隔时间固定,单位为ms。
若在调度任务执行时,上一次任务还未执行完毕,会加入worker队列,等待上一次执行完成后,马上执行下一次任务1000fixedRateString与fixedRate一致,只是间隔时间使用java.time.Duration#parse解析1000或PT1SfixedDelay固定延迟上一次任务执行结束到下一次执行开始的间隔时间固定,单位为ms。
1000fixedDelayString与fixedDelay一致,只是间隔时间使用java.time.Duration#parse解析1000或PT1SinitialDelay首次延迟多长时间后执行,单位ms。
之后按照fixedRate、fixedRateString、fixedDelay、fixedDelayString指定的规则执行,需要指定其中一个规则。
注意:不能和cron一起使用1000initialDelayString与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秒钟
@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());
}
}
单线程验证结果
:从执行时间和线程名可以看出两个定时任务是单线程,使用的是同一个线程执行的
增加配置类 :异步任务配置类进行线程池的设置
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
