Spring中的cron表达式常用于定时任务。
Spring中定时任务的分类@Nullable private ListtriggerTasks; @Nullable private List cronTasks; @Nullable private List fixedRateTasks; @Nullable private List fixedDelayTasks;
Spring 中的定时任务分为4种,
- triggerTask 触发task
- cronTask cron表达式的定时任务,
- fixedRateTask 固定频率执行的定时任务
- fixedDelayTask 固定延迟的Task
crontask即用cron表达式执行的task。
比如
@Scheduled(cron = "*/9 * * * * ?")
public void say() throws InterruptedException {
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
上面表达式是表示的是在秒数为 [0,9,18,27,36,45,54]秒的时候执行一次。比如打印
2022-06-22 16:26:18 2022-06-22 16:26:27 2022-06-22 16:26:36 2022-06-22 16:26:45 2022-06-22 16:26:54 2022-06-22 16:27:00 2022-06-22 16:27:09 2022-06-22 16:27:18
具体的算法实现在 org.springframework.scheduling.support.CronSequenceGenerator中。
fixedRateTasksfixedRateTask是按照固定频率跑的定时任务。
实现方式有两种。第一种fixRate,表示多少毫秒执行一次,下面代表3秒执行一次
@Scheduled(fixedRate = 3000)
public void say() throws InterruptedException {
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
另外一种是fixRateString
@Scheduled(fixedRateString = "PT3S")
public void say() throws InterruptedException {
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
上面PT3S 表示每隔3秒执行一次。和cron表达式不一样。这个比cron表达式更精确的每隔多少秒执行。
关于fixedRateString 的写法cron表达式算法是当前时间是否匹配cron表达式
fixRate 是根据上一次的开始时间来算下一次的运行时间
fixRateString 要符合下面 正则表达式
([-+]?)P(?:([-+]?[0-9]+)D)?" + "(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)
- 第一个负表示取整
- P是固定值 都得有
- D 代表天 ,比如P2D就代表2天
- (?[-+]?[0-9]+)H) 代表小时
- M代表分钟
- S代表秒
然后将这些链接起来,延迟多少时间。换算出的时间不能为负,具体解析方法为java.time.Duration#parse
fixedDelayTaskfixedDelay代表执行完成后多少秒执行任务
具体的使用方法为
@Scheduled(fixedDelay = 3000)
public void say() throws InterruptedException {
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
Thread.sleep(5000L);
}
比如上面的,就是8秒执行一次,
triggerTask触发器任务,通常用于自定义定时任务
比如需要实现一个按执行次数算延迟的定时任务,即执行几次就下一次就几秒后执行,那么就可以用triggerTask.代码如下
@Bean
public SchedulingConfigurer getSchedulingConfigurer(){
return taskRegistrar -> {
MyTask myTask = new MyTask();
taskRegistrar.setTriggerTasksList(Collections.singletonList(new TriggerTask(myTask, new MyTrigger(myTask))));
};
}
public static class MyTask implements Runnable{
int count = 0;
public int getCount(){
return count;
}
@Override
public void run() {
System.out.println("count : " + count++ + " date : "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
public static class MyTrigger implements Trigger{
private final MyTask myTask;
public MyTrigger(MyTask myTask){
this.myTask = myTask;
}
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
if(triggerContext.lastActualExecutionTime() == null){
return new Date();
}
return new Date(triggerContext.lastActualExecutionTime().getTime() + myTask.getCount() * 1000);
}
}
打印结果为
count : 1 date : 2022-06-23 13:58:41 count : 2 date : 2022-06-23 13:58:43 count : 3 date : 2022-06-23 13:58:46 count : 4 date : 2022-06-23 13:58:50 count : 5 date : 2022-06-23 13:58:55 count : 6 date : 2022-06-23 13:59:01 count : 7 date : 2022-06-23 13:59:08 count : 8 date : 2022-06-23 13:59:16 count : 9 date : 2022-06-23 13:59:25 count : 10 date : 2022-06-23 13:59:35 count : 11 date : 2022-06-23 13:59:46 count : 12 date : 2022-06-23 13:59:58 count : 13 date : 2022-06-23 14:00:11 count : 14 date : 2022-06-23 14:00:25定时任务中的线程池问题
我们经常用注解来实现定时任务,注解中的定时任务注册在org.springframework.scheduling.config.ScheduledTaskRegistrar 如果没有自定义线程池来执行定时任务,那么她将会默认采用单线程来执行,一旦有任务阻塞,那么会影响整体定时任务的执行效率。
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
如上,如果没定义线程池,spring默认采用的单线程来执行定时任务。
如何定义线程池if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");
try {
// Search for TaskScheduler bean...
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
}
catch (NoUniqueBeanDefinitionException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find unique TaskScheduler bean - attempting to resolve by name: " +
ex.getMessage());
}
try {
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isInfoEnabled()) {
logger.info("More than one TaskScheduler bean exists within the context, and " +
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
ex.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find default TaskScheduler bean - attempting to find ScheduledExecutorService: " +
ex.getMessage());
}
// Search for ScheduledExecutorService bean next...
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
}
catch (NoUniqueBeanDefinitionException ex2) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find unique ScheduledExecutorService bean - attempting to resolve by name: " +
ex2.getMessage());
}
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
}
catch (NoSuchBeanDefinitionException ex3) {
if (logger.isInfoEnabled()) {
logger.info("More than one ScheduledExecutorService bean exists within the context, and " +
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
ex2.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find default ScheduledExecutorService bean - falling back to default: " +
ex2.getMessage());
}
// Giving up -> falling back to default scheduler within the registrar...
logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
}
}
}
由上面源码可知,配置线程池中的步骤有下面几种
- 直接设置进 ScheduledTaskRegistrar 中,或者设置进ScheduledAnnotationBeanPostProcessor中
- 直接注入bean ,类型为 TaskScheduler
- 注入名称为 org.springframework.scheduling.TaskScheduler的bean
- 注入java.util.concurrent.ScheduledExecutorService 的bean
- 注入名称为java.util.concurrent.ScheduledExecutorService的bean
上面的优先级越靠前越优先。如果没有自定义,直接采用单线程的线程池。
例如 我们可以自定义TaskScheduler
@Bean
public TaskScheduler getTaskScheduler(){
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setErrorHandler(new ErrorHandler() {
@Override
public void handleError(Throwable t) {
System.out.println("error : " + t.getClass());
}
});
threadPoolTaskScheduler.setPoolSize(10);
return threadPoolTaskScheduler;
}
如何自定义定时任务中的错误处理类
我们需要知道定时任务中的报错原因。有的时候我们需要把错误日志上报等。我们可以采用自定义线程池中自定义错误handler类来处理,比如上面的代码 ,我们直接就能拿到Throwable.
在Spring中默认处理异常类是: org.springframework.scheduling.support.TaskUtils.LoggingErrorHandler 和 org.springframework.scheduling.support.TaskUtils.PropagatingErrorHandler



