目录
1.创建任务类
@Component
public class SchedulerTask1 {
private Log log= LogFactory.getLog(SchedulerTask1.class);
@Scheduled(cron = "0/5 * * * * *")
public void scheduled(){
//打印出当前时间和执行定时任务的线程名称
System.out.println("=====>>>>>使用cron {}======"+new Date().toString()+"======"+Thread.currentThread().getName());
}
@Scheduled(fixedRate=5000)
public void scheduled1() {
//打印出当前时间和执行定时任务的线程名称
System.out.println("=====>>>>>使用fixedRate{}==="+new Date().toString()+"======"+Thread.currentThread().getName());
}
@Scheduled(fixedDelayString = "${jobs.fixedDelay}")
public void scheduled2() {
//打印出当前时间和执行定时任务的线程名称
System.out.println("=====>>>>>fixedDelay{}======"+new Date().toString()+"======"+Thread.currentThread().getName());
}
}
2.开启定时任务
在Spring Boot中使用定时任务需要将@EnableScheduling注解添加到启动类上,否则不会启动定时任务:
@SpringBootApplication
@EnableScheduling
public class SchedulerdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SchedulerdemoApplication.class, args);
}
}
启动工程,查看定时任务打印出来的日志:
=====>>>>>使用cron {}======Fri Mar 15 17:08:15 CST 2019======scheduling-1
=====>>>>>使用fixedRate{}===Fri Mar 15 17:08:18 CST 2019======scheduling-1
=====>>>>>fixedDelay{}======Fri Mar 15 17:08:18 CST 2019======scheduling-1
=====>>>>>使用cron {}======Fri Mar 15 17:08:20 CST 2019======scheduling-1
=====>>>>>使用fixedRate{}===Fri Mar 15 17:08:23 CST 2019======scheduling-1
=====>>>>>fixedDelay{}======Fri Mar 15 17:08:23 CST 2019======scheduling-1
=====>>>>>使用cron {}======Fri Mar 15 17:08:25 CST 2019======scheduling-1
=====>>>>>使用fixedRate{}===Fri Mar 15 17:08:28 CST 2019======scheduling-1
=====>>>>>fixedDelay{}======Fri Mar 15 17:08:28 CST 2019======scheduling-1
我们可以在控制台的打印出来的日志信息中可以看到,我们定义的三个任务都已经出发并执行了,定时任务都是在同一个线程池用同一个线程来处理的。
3.执行时间的配置
在上面的定时任务中,我们在方法上使用@Scheduled注解来设置任务的执行时间,通过看 @Scheduled源码可以看出它支持多种参数:
- cron:cron表达式,指定任务在特定时间执行;
- fixedDelay:表示上一次任务执行完成后多久再次执行,参数类型为long,单位ms;
- fixedDelayString:与fixedDelay含义一样,只是参数类型变为String;
- fixedRate:表示按一定的频率执行任务,参数类型为long,单位ms;
- fixedRateString: 与fixedRate的含义一样,只是将参数类型变为String;
- initialDelay:表示延迟多久再第一次执行任务,参数类型为long,单位ms;
- initialDelayString:与initialDelay的含义一样,只是将参数类型变为String;
- zone:时区,默认为当前时区,一般没有用到;
4.多线程处理定时任务
看到控制台输出的结果,所有的定时任务都是通过一个线程来处理的,我估计是在定时任务的配置中设定了一个SingleThreadScheduledExecutor,于是我看了源码,从ScheduledAnnotationBeanPostProcessor类开始一路找下去。果然,在ScheduledTaskRegistrar(定时任务注册类)中的scheduleTasks()方法中有这样一段判断:
protected void scheduleTasks() {
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
....
}
这就说明如果taskScheduler为空,那么就给定时任务做了一个单线程的线程池,正好在这个类中还有一个设置taskScheduler的方法:
public void setScheduler(@Nullable Object scheduler) {
if (scheduler == null) {
this.taskScheduler = null;
}
else if (scheduler instanceof TaskScheduler) {
this.taskScheduler = (TaskScheduler) scheduler;
}
else if (scheduler instanceof ScheduledExecutorService) {
this.taskScheduler = new ConcurrentTaskScheduler(((ScheduledExecutorService) scheduler));
}
else {
throw new IllegalArgumentException("Unsupported scheduler type: " + scheduler.getClass());
}
}
这样问题就很简单了,我们只需用调用这个方法显式的设置一个ScheduledExecutorService就可以达到并发的效果了。我们要做的仅仅是实现SchedulingConfigurer接口,重写configureTasks方法就OK了:
@Configuration
public class ScheduleConfig implements SchedulingConfigurer{
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
//设定一个长度10的定时任务线程池
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
}
}
多线程并发执行定时任务结果:
=====>>>>>fixedDelay{}======Fri Mar 15 17:41:18 CST 2019======pool-1-thread-7
=====>>>>>使用cron {}======Fri Mar 15 17:41:20 CST 2019======pool-1-thread-8
=====>>>>>使用fixedRate{}===Fri Mar 15 17:41:23 CST 2019======pool-1-thread-4
=====>>>>>fixedDelay{}======Fri Mar 15 17:41:23 CST 2019======pool-1-thread-9
=====>>>>>使用cron {}======Fri Mar 15 17:41:25 CST 2019======pool-1-thread-3
=====>>>>>使用fixedRate{}===Fri Mar 15 17:41:28 CST 2019======pool-1-thread-10
=====>>>>>fixedDelay{}======Fri Mar 15 17:41:28 CST 2019======pool-1-thread-5
=====>>>>>使用cron {}======Fri Mar 15 17:41:30 CST 2019======pool-1-thread-6
还要一种配置多线程处理定时任务的方法
首先编写配置类AsyncConfig:
@SpringBootConfiguration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
// ThredPoolTaskExcutor的处理流程
// 当池子大小小于corePoolSize,就新建线程,并处理请求
// 当池子大小等于corePoolSize,把请求放入workQueue中,池子里的空闲线程就去workQueue中取任务并处理
// 当workQueue放不下任务时,就新建线程入池,并处理请求,如果池子大小撑到了maximumPoolSize,就用RejectedExecutionHandler来做拒绝处理
// 当池子的线程数大于corePoolSize时,多余的线程会等待keepAliveTime长时间,如果无请求可处理就自行销毁
ThreadPoolTaskExecutor threadPoolTaskExecutor=new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(5);//核心池大小
threadPoolTaskExecutor.setMaxPoolSize(10);//最大池大小
threadPoolTaskExecutor.setQueueCapacity(25);
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
// TODO Auto-generated method stub
return null;
}
}
在配置类上通过@SpringBootConfiguration表明是一个配置类,通过@EnableAsync开启多线程的开关。然后在定时任务的方法上添加@Async注解,当程序调用这个方法就会开启一个线程去执行具体定义的任务:
@Scheduled(fixedRate=5000)
@Async
public void scheduled1() {
//打印出当前时间和执行定时任务的线程名称
System.out.println("=====>>>>>使用fixedRate{}==="+new Date().toString()+"======"+Thread.currentThread().getName());
}
程序运行结果:
=====>>>>>使用fixedRate{}===Fri Mar 15 17:50:47 CST 2019======ThreadPoolTaskExecutor-4
=====>>>>>fixedDelay{}======Fri Mar 15 17:50:47 CST 2019======ThreadPoolTaskExecutor-5
=====>>>>>使用cron {}======Fri Mar 15 17:50:50 CST 2019======ThreadPoolTaskExecutor-2
=====>>>>>使用fixedRate{}===Fri Mar 15 17:50:52 CST 2019======ThreadPoolTaskExecutor-1
=====>>>>>fixedDelay{}======Fri Mar 15 17:50:52 CST 2019======ThreadPoolTaskExecutor-3
=====>>>>>使用cron {}======Fri Mar 15 17:50:55 CST 2019======ThreadPoolTaskExecutor-4