一。引入了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