应用场景
有贷款的同学每个月都会收到催还贷款的提醒短信,每天上班、上课前钉钉的打卡提醒,等等。类似这种定时重复的功能,我们就可以使用任务调度来实现。
任务调度框架
Spring Boot的@Scheduled注解
在单应用下,我们可以使用Spring Boot的@Scheduled注解来实现简单的任务调度。
首先需要使用@EnableScheduling注解开启任务调度
@RestController@SpringBootApplication(scanBasePackages = "com.yyoo")// 开启任务调度@EnableSchedulingpublic class Appliction {public static void main(String[] args) {SpringApplication.run(Appliction.class, args);}}
使用@Scheduled注解任务执行的方法
import org.springframework.scheduling.annotation.Scheduled;import org.ponent;@Componentpublic class TaskDemo1 {@Scheduled(cron = "1 * * * * ?")public void doTask1(){// 实际上就是每分钟一次System.out.println("每分钟的第1秒执行一次");}@Scheduled(cron = "1/1 * * * * ?")public void doTask2(){// 分子的1表示从第1秒开始执行,分母表示每隔1秒执行一次。// 但分子我们常常写为*,比如每10秒执行一次*/10// 如果需要每隔5秒执行一次,就是除以5System.out.println("每秒执行一次");}}
cron表达式
任务调度不可避开的要编写cron表达式,各个调度框架、包括linux服务器都有cron表达式来表示任务调度的时间、频率等。(注:各个框架或者linux服务器支持的cron表达式可能会有些许的不同。)
一下按照cron表达式从左至右的顺序
天和周不能同时表达具体的值,因为1号不一定是周1,所以我们的表达式最后那个周,用?。同理,如果我们要表达具体的周几,那么天这里就只能是?。
年通常我们都不怎么使用,所以我们的示例中只有6位,而没有最后一位年。
cron表达式中的符号
@Scheduled的其他用法
// @Scheduled(fixedDelay = 5000)// 时间默认单位是毫秒,我们可以通过timeUnit属性修改@Scheduled(fixedDelay = 5,timeUnit = TimeUnit.SECONDS)public void doTask3() throws InterruptedException {Thread.sleep(10000);// 休眠10秒来验证(意味着至少要等15秒之后才会再次执行)// 上次调用结束后5秒执行System.out.println("上次调用结束后5秒执行"+Thread.currentThread().getName());}@Scheduled(fixedRate = 5,timeUnit = TimeUnit.SECONDS)public void doTask4() throws InterruptedException {// 每隔5秒执行,无论上次执行是否成功,是否结束System.out.println("每隔5秒执行"+Thread.currentThread().getName());Thread.sleep(10000);}
细心一点的同学运行程序后会发现,我们目前定义了4个任务,貌似执行起来已经不太对劲了。比如每秒执行的任务很久都不执行了。why?
如果我们每个任务方法中都打线程名称后你会发现,打印出的值都是:scheduling-1。说明我们的所有任务都是同一个线程执行的。导致了我们的任务串行化了。此时我们可以通过@Async注解来解决。
配置ThreadPoolTaskScheduler来解决串行化
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.TaskScheduler;import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;@Configurationpublic class ScheduleConfig {@Beanpublic TaskScheduler taskScheduler(){ThreadPoolTaskScheduler tpts = new ThreadPoolTaskScheduler();tpts.setPoolSize(5);// 线程池数量tpts.setThreadNamePrefix("my-Task-");// 线程名称前缀return tpts;}}
@Async异步执行任务调度
与@Scheduled一样,@Async也需要一个注解来开启。在Application类上添加@EnableAsync注解来开启@Async注解支持。最后我们的示例代码如下:
import org.springframework.scheduling.annotation.Async;import org.springframework.scheduling.annotation.Scheduled;import org.ponent;import java.util.concurrent.TimeUnit;@Componentpublic class TaskDemo1 {@Scheduled(cron = "1 * * * * ?")@Asyncpublic void doTask1(){// 实际上就是每分钟一次System.out.println("每分钟的第1秒执行一次"+Thread.currentThread().getName());}@Scheduled(cron = "1/1 * * * * ?")@Asyncpublic void doTask2(){// 分子的1表示从第1秒开始执行,分母表示每隔1秒执行一次。// 但分子我们常常写为*,比如每10秒执行一次*/10// 如果需要每隔5秒执行一次,就是除以5System.out.println("每秒执行一次"+Thread.currentThread().getName());}// @Scheduled(fixedDelay = 5000)// 时间默认单位是毫秒,我们可以通过timeUnit属性修改@Scheduled(fixedDelay = 5,timeUnit = TimeUnit.SECONDS)@Asyncpublic void doTask3() throws InterruptedException {Thread.sleep(10000);// 休眠10秒来验证(意味着至少要等15秒之后才会再次执行)// 上次调用结束后5秒执行System.out.println("上次调用结束后5秒执行"+Thread.currentThread().getName());}@Scheduled(fixedRate = 5,timeUnit = TimeUnit.SECONDS)@Asyncpublic void doTask4() throws InterruptedException {// 每隔5秒执行,无论上次执行是否成功,是否结束System.out.println("每隔5秒执行"+Thread.currentThread().getName());Thread.sleep(10000);}}
执行后打印结果正确了,而且线程名字也不一样了。
@Async注释的方法还可以定义异步Future返回
@AsyncFuture<String> returnSomething(int i) {// this will be run asynchronously}
使用@Async来异步调用之后,我们之前配置的ThreadPoolTaskScheduler就没有效果了。因为你会看到有第6、第7甚至第8个线程出现。使用@Async来处理异步任务调度,spring自动控制线程的数量,如果需要自己控制,就自定义配置ThreadPoolTaskScheduler把。
上一篇:Spring Boot logback日志
下一篇:待续