什么是定时任务,使用场景:
- 某个时间定时处理某个任务
- 发邮件、短信等
- 消息提醒
- 订单通知
- 统计报表系统
- ...
常见定时任务
- Java自带的java.util.Timer类配置比较麻烦,时间延后问题
- Quartz框架: 配置更简单,xml或者注解适合分布式或者大型调度作业
- SpringBoot框架自带
SpringBoot使用注解方式开启定时任务:
- 启动类里面 @EnableScheduling开启定时任务,自动扫描
- 定时任务业务类 加注解 @Component被容器扫描
- 定时执行的方法加上注解 @Scheduled(fixedRate=2000) 定期执行一次
- 补充题外: @PostConstruct 只执行一次(是Java自带的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。)
- 定时任务默认是单线程的
Scheduled 参数设置:
-
cron 定时任务表达式 @Scheduled(cron="*/1 * * * * *") 表示每秒 (上一个任务未完成 下一个任务排队)
- crontab 工具 crontab执行时间计算 - 在线工具
-
fixedRate: 定时多久执行一次(上一次开始执行时间点后xx秒再次执行)
- (fixedRate有个坑,单线程的时候当任务执行时长超过设置的间隔时长时 后续任务会被阻塞 只有前一个任务执行完成后 后续任务就会立马执行 举个例子:(假如设置的fixedRate=3*1000,理论上 但是当第一个任务开始时 第三秒后就会调用第二次任务 但是第一个任务开始到结束用了7秒 ,所以开始第一个任务到结束 中途重新调用了两次任务 但第二个任务 和 第三个任务都被阻塞了 第二个和第三个任务就在排队 当第一个任务结束后 第二个任务就睡立马执行))
-
解决@Scheduled(fixedRate)如何避免任务被阻塞
-
加上注解
@EnableAsync
(类上)和@Async
(方法上),加了注解以后,就开启了多线程模式,当到了下一次任务的执行时机时,如果上一次任务还没执行完,就会自动创建一个新的线程来执行它。异步执行也可以理解为保证了任务以固定速度执行。
-
-
fixedDelay: 上一次执行结束时间点后xx秒再次执行
例子:
@Component
public class RecruitContract {
@Scheduled(cron="*/1 * * * * *")
public void explainLogs(){
System.out.println("每1秒执行一次");
}
//@Async 异步 启动类需要加 @EnableAsync (开启异步)
@Scheduled(fixedRate = 3 * 1000)
public void explainLogs(){
System.out.println("上一个任务开始后 3秒执行");
}
@Scheduled(fixedDelay= 3 * 1000)
public void explainLogs(){
System.out.println("上一个任务结束三秒后 3秒执行 ");
}
@PostConstruct
public void explainLogs(){
System.out.println("项目启动执行一次");
}
}
@SpringBootApplication
//@EnableAsync (开启异步)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
SpringBoot异步任务EnableAsync:
JUC-04 中的异步回调有补充
-
什么是异步任务
-
异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。
-
-
使用场景:适用于处理log、发送邮件、短信……等()
- 下单接口->查库存 1000
- 余额校验 1500
- 风控用户1000
-
启动类里面使用@EnableAsync注解开启功能,自动扫描
-
定义异步任务类并使用@Component标记组件被容器扫描,异步方法加上@Async(也可以加在类上 代表该类所有方法都是异步方法)
实例:
@Component
@async
public class AsyncTask {
public void task1() {
try {
Thread.sleep(4000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" task 1 ");
}
public void task2() {
try {
Thread.sleep(4000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" task 2 ");
}
}
//在同一个类的同一个方法里调用
task1();
task2();
运行时间 大概在4S
使用SpringBoot开发异步任务Future获取结果:
-
注意点:
- 要把异步任务封装到类里面,不能直接写到Controller
- 增加Future 返回结果 AsyncResult("task执行完成");
- 如果需要拿到结果 需要判断全部的 task.isDone()
@Component
@async
public class AsyncTask {
public Future<String> task4() {
try {
Thread.sleep(4000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" task 4 ");
return new AsyncResult<String>( "task5");
}
public Future<String> task5() {
try {
Thread.sleep(4000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" task 5 ");
return new AsyncResult<String>( "task5");
}
}
同一个类里同一个方法调用
Future<String> task4 =asyncTask.task4();
Future<String> task5 =asyncTask.task5();
for(;;){
//判断所有异步方法是否执行完毕
if(task4.isDone() && task5.isDone()){
try{
String task4Result = task4.get();
System.out.printin(task4Result):
String task5Result = task5.get();
System.out.println(task5Result);
} catch (InterruptedException e){
e.printStackTrace();
} catch (ExecutionException e){
e.printStackTrace();
}finally {
break;
}
}
}