相关文章
前言
今天来学习一下通过 SpringBoot 来实现一个定时任务和异步调用。
定时任务
在 Spring 中 可以通过 @EnableScheduling 和 @Scheduled 这两个注解来实现我们的定时任务,接下来看下这两个注解:
@EnableScheduling
在配置类上标注了 @EnableScheduling 注解后,即表示 Spring 开启了定时任务,在 Spring 容器中标注了 @Scheduled 注解的方法都会被检测到,会进行执行;如果需要更多更加细粒度的控制,配置类可以实现 SchedulingConfigurer 类来进行实现,如下所示:
@Configuration
@EnableScheduling
public class MyScheduleTaskConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
// taskRegistrar.setCronTasks();
// taskRegistrar.setFixedDelayTasks();
// taskRegistrar.setTriggerTasksList();
}
@Bean(destroyMethod="shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(100);
}
}
@Scheduled
在 Spring 中开启了定时任务后,就可以使用 @Scheduled 这个注解来定义需要定时执行的方法了,接下来看下它的一个定义:
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
// cron 表达式
String cron() default "";
// 时区
String zone() default "";
// 时间间隔 单位毫秒
long fixedDelay() default -1;
// 时间间隔,单位毫秒,使用 String 表示
String fixedDelayString() default "";
// 时间间隔 单位毫秒
long fixedRate() default -1;
// 时间间隔,单位毫秒,使用 String 表示
String fixedRateString() default "";
// 初始化时间,即任务在多少秒后开始执行第一次
long initialDelay() default -1;
// 初始化时间,即任务在多少秒后开始执行第一次,使用 String 表示
String initialDelayString() default "";
}
接下来,就来测试一下 cron ,fixedDelay 和 fixedRate 这三个属性:
cron
cron 属性,即使用 cron 表达式来控制任务的定时执行,如下所示:
/**
* 定时任务
*/
@Component
public class ScheduledTask {
private static final DateFormat dateFormat = new SimpleDateFormat("HH:m:ss");
@Scheduled(cron = "0/5 * * * * ?")
public void printTime3() throws InterruptedException {
System.out.println("time: " + dateFormat.format(new Date()));
}
}
启动程序,查看控制台打印,会 5 秒打印一次:
time: 14:19:00
time: 14:19:05
time: 14:19:10
time: 14:19:15
如果,任务在 5 秒之内已经执行完毕,会怎么样呢?
@Scheduled(cron = "0/5 * * * * ?")
public void printTime3() throws InterruptedException {
System.out.println("time: " + dateFormat.format(new Date()));
Thread.sleep(2000);
}
time: 14:20:50
time: 14:20:55
time: 14:21:00
time: 14:21:05
由打印的结果看到,如果任务在间隔时间之内已经执行完毕,则下一个任务也会等到时间间隔结束后才会执行下一次任务。
如果,任务在 5 秒之内还没有执行完,则会怎么样呢?
@Scheduled(cron = "0/5 * * * * ?")
public void printTime3() throws InterruptedException {
System.out.println("time: " + dateFormat.format(new Date()));
Thread.sleep(8000);
}
time: 14:28:25
time: 14:28:35
time: 14:28:45
time: 14:28:55
由打印结果可以看到,如果任务在时间间隔之内没有执行完毕,则下一个任务会留到下一个周期才会执行,因为 Spring 在处理使用cron表达式这种定时任务时,它会在任务开始时判断任务是否可以执行,如果可以则执行,如果不可以,那么它将不执行此次任务,等待下一次执行。
fixedDelay
fixedDelay 属性表示的是从上一个任务结束到下一个任务开始之间的时间间隔,如果上一个任务执行超时了,下一个任务会等待上一个任务执行完毕后才执行:
@Scheduled(fixedDelay = 5000)
public void printTime2() throws InterruptedException {
System.out.println("time: " + dateFormat.format(new Date()));
}
time: 14:38:29
time: 14:38:34
time: 14:38:39
time: 14:38:44
time: 14:38:49
如果,任务在时间间隔内执行完毕:
@Scheduled(fixedDelay = 5000)
public void printTime2() throws InterruptedException {
System.out.println("time: " + dateFormat.format(new Date()));
Thread.sleep(2000);
}
每隔 7 秒打印一次
time: 14:40:24
time: 14:40:31
time: 14:40:38
time: 14:40:45
time: 14:40:52
从打印结果可以看到,如果任务在时间间隔之内已经执行完毕,下一个任务等待的时间将会是任务执行的时间+时间间隔;
如果,任务在之间间隔之内没有执行完毕:
@Scheduled(fixedDelay = 5000)
public void printTime2() throws InterruptedException {
System.out.println("time: " + dateFormat.format(new Date()));
Thread.sleep(8000);
}
每隔 13 秒打印一次
time: 14:44:20
time: 14:44:33
time: 14:44:46
time: 14:44:59
time: 14:45:12
从打印结果可以看到,如果任务在时间间隔之内还没有执行完毕,下一个任务等待的时间将会是任务执行的时间+时间间隔;
从上面两个例子可以看到,fixedDelay 主要关注的是上一个任务结束的时间到下一个任务开始的时间之间的时间间隔,也就是下一个任务开始执行前需要等待的时间=上一个任务执行时间+时间间隔
fixedRate
fixedRate 表示的是两个任务开始执行的时间间隔,
@Scheduled(fixedRate = 5000)
public void printTime() throws InterruptedException {
System.out.println("time: " + dateFormat.format(new Date()));
}
time: 14:53:38
time: 14:53:43
time: 14:53:48
time: 14:53:53
time: 14:53:58
和上面的 cron 和 fixedDelay 一样,都是 5 秒执行一次。
如果,任务在时间间隔之内已经执行完毕,如下:
@Scheduled(fixedRate = 5000)
public void printTime() throws InterruptedException {
System.out.println("time: " + dateFormat.format(new Date()));
Thread.sleep(2000);
}
time: 14:55:04
time: 14:55:09
time: 14:55:14
time: 14:55:19
time: 14:55:24
可以看到,这里就和 fixedDelay 不一样了,fixedDelay 下一个任务等待的时间等于上一个任务执行时间+时间间隔,而这里,从打印的结果可以看到,如果上一个任务在时间间隔之内已经执行完毕,则下一个任务还是会等待到时间间隔结束后才会执行。说明,fixedRate 表示的时间间隔是两个任务开始的时间间隔。
如果,任务在时间间隔之内没有执行完毕;
@Scheduled(fixedRate = 5000)
public void printTime() throws InterruptedException {
System.out.println("time: " + dateFormat.format(new Date()));
Thread.sleep(8000);
}
time: 15:0:24
time: 15:0:32
time: 15:0:40
time: 15:0:48
time: 15:0:56
从打印结果可以看到,如果任务在时间间隔之内还没有执行完毕,则下一个任务会等待上一个任务执行完毕后才开始执行,此时时间间隔不再是 5秒而是8秒。
结论
1、fixedRate 强调的是上一次任务的开始时间 到 下一次任务的开始时间的间隔
2、fixedDelay 调的是上一次任务的结束时间 到 下一次任务的开始时间的间隔
3、cron表达式配置了在哪一刻执行任务,会在任务开始时判断任务是否可以执行,如果能则执行,不能则会跳过本次执行
异步调用
在 Spring Boot 中通过 @EnableAsync 和 @Async 这两个注解来实现异步调用的,接下来看下这两个注解:
@EnableAsync
@EnableAsync 该注解主要是用来开启Spring 异步调用的,用在配置类上,
@SpringBootApplication
@EnableAsync
public class MyspringbootApplication {
public static void main(String[] args) {
SpringApplication.run(MyspringbootApplication.class, args);
}
}
@Async
当 Spring 开启了 异步调用后,就可以使用 @Async 定义需要异步执行的方法了,
/**
* 异步调用任务
*/
@Component
public class AsyncTask {
private Random random = new Random();
@Async
public void task1() throws InterruptedException {
System.out.println("任务一开始...");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
System.out.println("任务一耗时:" + (System.currentTimeMillis() - start));
System.out.println("任务一完成...");
}
@Async
public void task2() throws InterruptedException {
System.out.println("任务二开始...");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
System.out.println("任务二耗时:" + (System.currentTimeMillis() - start));
System.out.println("任务二完成...");
}
@Async
public void task3() throws InterruptedException {
System.out.println("任务三开始...");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
System.out.println("任务三耗时:" + (System.currentTimeMillis() - start));
System.out.println("任务三完成...");
}
}
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyspringbootApplication.class)
public class MyTest {
@Autowired
private AsyncTask task;
@Test
public void test() throws InterruptedException {
task.task1();
task.task2();
task.task3();
Thread.sleep(60 * 1 * 1000);
}
}
任务一开始...
任务三开始...
任务二开始...
任务三耗时:2018
任务三完成...
任务一耗时:4027
任务一完成...
任务二耗时:9364
任务二完成...
如果不想使用默认的线程池,我们还可以自定义线程池,只需要实现 AsyncConfigurer 即可:
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Bean(name = "taskExecutor")
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.setThreadNamePrefix("MyExecutor-");
executor.initialize();
// 关闭资源
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
使用自定义线程池:
@Async("taskExecutor")
public void task1() throws InterruptedException {
System.out.println("任务一开始...");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
System.out.println("任务一耗时:" + (System.currentTimeMillis() - start));
System.out.println("任务一完成...");
}
在线程执行的时候,可以有返回值 Future,我们可以返回 AsyncResult:
@Async("taskExecutor")
public Future<String> test4(){
System.out.println("执行任务,返回结果");
return new AsyncResult<>("任务执行结果:5");
}
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyspringbootApplication.class)
public class MyTest {
@Autowired
private AsyncTask task;
@Test
public void test2() throws InterruptedException, ExecutionException, TimeoutException {
Future<String> future = task.test4();
System.out.println("任务是否完成:" + future.isDone());
String result = future.get(5, TimeUnit.SECONDS);
System.out.println(result);
System.out.println("任务是否完成:" + future.isDone());
}
}
结果:
任务是否完成:false
执行任务,返回结果
任务执行结果:5
任务是否完成:true
以上就是通过 SpringBoot 来操作定时任务和异步调用。