1. 开启支持——在启动类上加注解
由于Spring Schedule已经包含在spring-boot-starter基础模块中了,所以不需要增加额外的依赖。
@SpringBootApplication
@EnableScheduling <------这里加注解开启
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
2. 建立任务类——执行周期性的工作
@Component <------这里要加注解
public class MyWork {
@Scheduled(fixedRate = 5000)
public void aWork() {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss");
System.out.println("每隔五秒钟遇见一个美女:" + sdf.format(date));
}
}
3. 定时任务的两种类型
- 间隔执行
@Scheduled(fixedRate = 5000) // 表示每隔 5000 毫秒执行一次
public void reportCurrentTime() {
System.out.println("每隔五秒钟执行一次: " + dateFormat.format(new Date()));
}
- 定时执行
@Scheduled(cron = "0 30 11 ? * *") // 表示在指定时间执行
public void fixTimeExecution() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss");
System.out.println("在指定时间 " + sdf.format(new Date()) + "执行");
}
cron表达式的解释如下
4. 自定义线程池
- springboot默认使用单线程池调度任务。
protected void scheduleTasks() {
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
//省略...
}
对于多个任务而言,需要增加线程池的线程数量,避免任务都放在一个单线程中串行地执行,导致任务执行时间过长,运行的时效性不能保障。
注意:当方法的执行时间超过任务调度周期时,调度器会在下个周期执行方法内容。
- 自定义线程池
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean(destroyMethod="shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(20);
}
}
当然使定时任务并行执行还可以使用 @EnableAsync 和 @Async 这两个注解。
@Component
@EnableAsync
public class AsyncScheduledTasks {
private static final Logger log = LoggerFactory.getLogger(AsyncScheduledTasks.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
@Async
@Scheduled(fixedDelay = 2000)
public void reportCurrentTimeWithFixedDelay() {
try {
TimeUnit.SECONDS.sleep(3);
log.info("Fixed Delay Task : The time is now {}", dateFormat.format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
5. Web应用中的启动和关闭问题
我们知道通过spring加载或初始化的Bean,在服务停止的时候,spring会自动卸载(销毁)。但是由于线程是JVM级别的,如果用户在Web应用中启动了一个线程,那么这个线程的生命周期并不会和Web应用保持一致。也就是说,即使Web应用停止了,这个线程依然没有结束(死亡)。
解决方法:
- 当前对象是通过spring初始化
spring在卸载(销毁)实例时,会调用实例的destroy方法。通过实现DisposableBean接口覆盖destroy方法实现。在destroy方法中主动关闭线程。
@Component
public class MyTask implements DisposableBean{
@Override
public void destroy() throws Exception {
//关闭线程或线程池
ThreadPoolTaskScheduler scheduler = (ThreadPoolTaskScheduler)applicationContext.getBean("scheduler");
scheduler.shutdown();
}
//省略...
}
- 当前对象不是通过spring初始化(管理)
那么我们可以增加一个Servlet上下文监听器,在Servlet服务停止的时候主动关闭线程。
public class MyTaskListenter implements ServletContextListener{
@Override
public void contextDestroyed(ServletContextEvent arg0) {
//关闭线程或线程池
}
//省略...
}
参考资料
- https://www.cnblogs.com/skychenjiajun/p/9057379.html?utm_source=tuicool&utm_medium=referral
- 如果希望动态设定并提交定时任务,可以参考这里