SpringBoot实现定时任务

一、前言

最近在公司接触到了没有遇到过的需求,就是创建一个指定时间间隔的定时任务定时去调用一个算法模型,并保存计算结果文件,其次就是该定时任务所对应的多条历史计算记录会被保存。

二、实现方案总览

1.Timer:这是Java 1.3自带的定时任务类,位于java.util.Timer,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照一个指定的频度执行,但不能在指定时间运行,比较少用。

2.ScheduledExecutorService:这个同样的JDK自带的类,但是是基于线程池设计的定时任务类,每个调度任务都会被分配到线程池中的一个线程去执行,可并发执行。

3.Spring Task:这个是Spring 3.0后spring框架提供的一种任务调度和定时任务执行的机制。它通过TaskExecution来实现任务的执行,可以在指定时间或周期性地执行任务。它还提供了其他配置选项,如任务池、错误处理等,以及一些便利的注解和工具类来简化任务调度的配置和使用。可视为一个轻量级的Quartz框架。

4.Quartz:这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂

三、技术选型和示例说明

由于Timer的使用范围太狭小,不能指定时间周期来执行任务,而Quartz配置过于复杂,上手难,所以我是基于Spring Task进行任务的调度和定时任务的执行的。

3.1基于注解实现

spring提供了一个定时任务的注解@Scheduled,使用注解标记方法,并配置任务执行的时间表达式(如 cron 表达式、固定延迟、固定速率等),Spring 框架会自动执行注解标记的方法。

ps:使用该注解前需要在启动类上加上@EnableScheduling,Spring 框架会自动扫描带有@Scheduled注解的方法,并在指定的时间执行。

@Scheduled(cron = "0 0 0 * * ?") // 每天零点执行
public void myScheduledTask() {
    // 执行任务逻辑
}

3.2基于线程池(ThreadPoolTaskScheduler)实现

使用Spring提供的注解配置定时任务固然方便简易,但如果碰到有些业务要求通过前端某个按钮启动定时任务以及主动终止任务,那么上面基于注解的方式就可能不太合适了。通过线程池提供一个接口暴露给前端,前端调用后提交一个任务分配给线程池去处理,并返回状态缓存变量,通过此变量可对任务做出终止的操作。

下面是使用实例:

ThreadPoolTaskScheduler是Spring框架提供的一个线程池任务调度器。实现了TaskScheduler接口,并且基于线程池来执行定时任务。使用ThreadPoolTaskScheduler,你可以在Spring应用程序中方便地创建和管理多个线程,并使用这些线程来执行定时任务。它提供了灵活的配置选项,可以设置线程池的大小、线程优先级、任务队列等属性,以满足不同场景下的任务调度需求。

ThreadPoolTaskScheduler 四个启动定时任务的API:

  • schedule(Runnable task, Date startTime),在指定时间执行一次定时任务
  • schedule(Runnable task, Trigger trigger),动态创建指定克隆cron表达式执行定时任务
  • scheduleAtFixedRate,指定间隔时间执行一次任务,间隔时间为前一次执行开始到下次任务开始时间
  • scheduleWithFixedDelay,指定间隔时间执行一次任务,间隔时间为前一次完成到下一次开始时间

上面需要注意scheduleAtFixedRatescheduleWithFixedDelay区别

*scheduleAtFixedRate:指定任务在固定的时间间隔内执行,即若任务的执行时间超过了时间间隔,则不等待上一次任务执行结束就马上开始下一次任务的执行。

*scheduleWithFixedDelay:指定任务在上次任务执行完成之后,再经过固定的时间间隔后开始下一次任务的执行。

基本的类和API都说明完了,代码正式开始

首先我们需要向Spring容器注册一个ThreadPoolTaskScheduler的Bean对象,用于调度定时任务,为了能够手动控制任务的启动与终止,需要定义一个变量来引用追踪当前执行的任务,调用API提交完定时后返回值是ScheduleFuture对象,它是一个定时任务的状态变量对象,里面提供了可以终止任务的API。

spring默认实现的线程池的线程池大小为1,如果多个任务被触发,会等待第一个任务执行完毕才会执行队列中的下一个任务,所以还是需要自己去定义线程池的大小,避免默认线程池带来一些错误。另外,基于注解的方式@Scheduled也是通过默认的线程池ThreadPoolScheduler来实现的,所以线程池大小也同样为1


@Configuration
public class ScheduleConfig {
    

    //定时任务的未来结果状态变量 K:任务名称 V:状态变量
    public static ConcurrentHashMap<String,ScheduledFuture<?>> futureHashMap = new ConcurrentHashMap<>();

    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){

        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(10); //核心线程
        threadPoolTaskExecutor.setMaxPoolSize(12);  //最大线程数
        threadPoolTaskExecutor.setQueueCapacity(10); //队列大小
        // 设置线程池的拒绝策略,用于处理无法接收新任务的情况,默认策略为AbortPolicy
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        // 设置线程池中空闲线程的存活时间
        threadPoolTaskExecutor.setKeepAliveSeconds(60);
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }

}

接着通过Controller暴露接口给前端,提交并启动定时任务

  Long taskPeriod = TaskUtils.getTaskTimeInterval(scheduledTaskDO);
//        Long taskPeriod = getTaskTimeInterval(scheduledTaskDO);
        ScheduledFuture<?> schedule = threadPoolTaskScheduler.scheduleAtFixedRate(new ScheduleTaskRunnable(scheduledTaskService,historyScheduledTaskService,taskId,taskName),taskPeriod);

        //保存任务状态
        futureHashMap.put(taskName,schedule);

后续通过任务名称获取对应任务的任务状态缓存变量,然后可进行终止操作

 ScheduledFuture<?> scheduledFuture = futureHashMap.get(taskName);

        Boolean isCancel = false;
        if (scheduledFuture != null){
            isCancel = scheduledFuture.cancel(true);
        }

四、结语

springboot整合定时任务大概的内容就是这样,后续还有内容会继续补充。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值