栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring中定时任务的技术内幕

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring中定时任务的技术内幕

Spring定时任务详解

Spring中的cron表达式常用于定时任务。

Spring中定时任务的分类
	@Nullable
	private List triggerTasks;

	@Nullable
	private List cronTasks;

	@Nullable
	private List fixedRateTasks;

	@Nullable
	private List fixedDelayTasks;

Spring 中的定时任务分为4种,

  1. triggerTask 触发task
  2. cronTask cron表达式的定时任务,
  3. fixedRateTask 固定频率执行的定时任务
  4. fixedDelayTask 固定延迟的Task
CronTask

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中。

fixedRateTasks

fixedRateTask是按照固定频率跑的定时任务。

实现方式有两种。第一种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表达式更精确的每隔多少秒执行。

cron表达式算法是当前时间是否匹配cron表达式

fixRate 是根据上一次的开始时间来算下一次的运行时间

关于fixedRateString 的写法

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

fixedDelayTask

fixedDelay代表执行完成后多少秒执行任务

具体的使用方法为

@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");
				}
			}
		}

由上面源码可知,配置线程池中的步骤有下面几种

  1. 直接设置进 ScheduledTaskRegistrar 中,或者设置进ScheduledAnnotationBeanPostProcessor中
  2. 直接注入bean ,类型为 TaskScheduler
  3. 注入名称为 org.springframework.scheduling.TaskScheduler的bean
  4. 注入java.util.concurrent.ScheduledExecutorService 的bean
  5. 注入名称为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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/988152.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号