SpringBoot定时任务Scheduled简易使用及动态多任务使用

2023年2月19日09:26:52

一。引入了spring-boot-starter包即可,无需额外jar包:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

二。启动类添加注解@EnableScheduling:

@SpringBootApplication//@EnableAutoConfiguration 
@EnableScheduling//支持定时任务
public class SpringBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootApplication.class, args);
    }

}

三。定时任务代码编写:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Date;


@Component
public class DemoSchedule {

    @Scheduled(cron = "0 0/1 * * * ?")//每隔一分钟执行
    public void test1() {
        System.out.println("定时任务执行:" + new Date());
    }
    
}

其中,cron表达式是spring内置支持的时间表达式,详解待续,可百度“在线cron”,帮你自动生成。 

 

上面使用的定时任务很简单,但是有缺点:

多个定时任务使用的是同一个调度线程,所以任务是阻塞执行的,执行效率不高。如果出现任务阻塞,导致一些场景的定时计算没有实际意义,比如每天12点的一个计算任务被阻塞到1点去执行,会导致结果并非我们想要的。

解决方式1:替换默认线程池,默认线程池只有一个线程:

@Configuration
public class ScheduledConfig implements SchedulingConfigurer {

    @Autowired
    private TaskScheduler myThreadPoolTaskScheduler;

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        //简单粗暴的方式直接指定
        //scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(2));
        //也可以自定义的线程池,方便线程的使用与维护
        scheduledTaskRegistrar.setTaskScheduler(myThreadPoolTaskScheduler);
    }

    @Bean(name = "myThreadPoolTaskScheduler")
    public TaskScheduler getMyThreadPoolTaskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(20);//定时任务数量需小于池大小。否则,还是会有定时任务等待执行
        taskScheduler.setThreadNamePrefix("taskScheduler-");
        taskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //调度器shutdown被调用时等待当前被调度的任务完成
        taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        //等待时长
        taskScheduler.setAwaitTerminationSeconds(60);
        return taskScheduler;
    }
}

解决方式2:方式一的本质改变了任务调度器默认使用的线程池,接下来这种是不改变调度器的默认线程池,而是把任务交给一个异步线程池去执行:

首先在启动类上添加@EnableAsync 注解开启异步任务支持
然后在定时任务的方法加上@Async即可,默认使用的线程池为SimpleAsyncTaskExecutor(该线程池默认来一个任务创建一个线程,就会不断创建大量线程,极有可能压爆服务器内存。当然它有自己的限流机制,这里就不多说了)
项目中为了更好的控制线程的使用,我们可以自定义我们自己的线程池,使用方式@Async("myThreadPool")

  @Scheduled(fixedRate = 1000*10,initialDelay = 1000*20)
  @Async("myThreadPoolTaskExecutor")
  //@Async
  public void scheduledTest02(){
      System.out.println(Thread.currentThread().getName()+"--->xxxxx--->"+Thread.currentThread().getId());
  }

  //自定义线程池
  @Bean(name = "myThreadPoolTaskExecutor")
  public TaskExecutor  getMyThreadPoolTaskExecutor() {
      ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
      taskExecutor.setCorePoolSize(20);//定时任务能够同时执行的线程数。定时任务数量需小于池大小。否则,还是会有定时任务等待执行
      taskExecutor.setMaxPoolSize(200);//定时任务能被同时调度的数量。调度后不一定马上执行,取决于上面的corePoolSize
      taskExecutor.setQueueCapacity(25);
      taskExecutor.setKeepAliveSeconds(200);
      taskExecutor.setThreadNamePrefix("Haina-ThreadPool-");
      // 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
      taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
      //调度器shutdown被调用时等待当前被调度的任务完成
      taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
      //等待时长
      taskExecutor.setAwaitTerminationSeconds(60);
      taskExecutor.initialize();
      return taskExecutor;
  }

 

上面能够解决多定时任务的阻塞问题,但如果多定时任务同时还是动态的呢?即cron表达式动态传入呢?

1.自定义定时任务调度线程池注入spring。默认线程池只有一个线程。

2.实现SchedulingConfigurer接口。多个任务编写多个类实现SchedulingConfigurer接口即可。

@Configuration
public class ThreadPoolTaskSchedulerConfig {

    //自定义定时任务调度线程池注入spring
    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(20);//定时任务数量需小于池大小。否则,可能会有定时任务等待执行
        threadPoolTaskScheduler.setThreadNamePrefix("taskScheduler-");
        threadPoolTaskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);//调度器shutdown被调用时等待当前被调度的任务完成
        threadPoolTaskScheduler.setAwaitTerminationSeconds(60);//等待时长
        return threadPoolTaskScheduler;
    }

}
@Component
public class MySchedulingConfigurer implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.addTriggerTask(task(), trigger());
    }

    private Runnable task() {
        return new Runnable() {
            @Override
            public void run() {
                //业务逻辑部分
                System.out.println("task1 ==== I am going:" + LocalDateTime.now());
            }
        };
    }

    private Trigger trigger() {
        return new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                //每一次任务触发,都会执行这里的方法一次,重新获取下一次的执行时间。所以它是下下次才生效的,即不是实时生效
                String cron = "0/10 * * * * ?";//10秒执行一次,动态注入:这里改为从数据库获取表达式就可以了
                CronTrigger cronTrigger = new CronTrigger(cron);
                Date date = cronTrigger.nextExecutionTime(triggerContext);
                return date;
            }
        };
    }

}

以上spring task动态定时任务缺点很明显,时间修改后不能马上生效,要等到下次执行加载Trigger后更新cron,下下次才会生效,所以想要即时生效就得用Quartz了!可参考:https://blog.csdn.net/yzh_1346983557/article/details/103990044

  • 作者:yzh_1346983557
  • 原文链接:https://blog.csdn.net/yzh_1346983557/article/details/86244486
    更新时间:2023年2月19日09:26:52 ,共 4612 字。