1. 创建定时任务业务类,如 MyTask
2. 在 MyTask
上添加 Component
注解以作为组件能被扫描到。
3. 在定时任务方法上添加 @Scheduled
以指明执行该任务的时机
package top.zhenganwen.springbootmybatis.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MyTask {
@Scheduled(fixedRate = 2000) //每隔两秒执行一次
public void task1() {
System.out.println(new Date());
}
}
4. 在启动类上添加 @EnableScheduling
以使组件中的 @Scheduled
注解生效。
定时任务的核心就在于任务执行的时机,即 @Scheduled
注解的使用。该注解的属性有 cron
、zone
、fixedDelay
、fixedDelayString
、fixedRate
、fixedRateString
、initialDelay
、initialDelayString
cron表达式是一种定位时间点的表达式。如 @Scheduled(cron="*/1 * * * * *")
表示每秒,其用法大家自行查阅。
cron表达式在线生成工具: https://tool.lu/crontab/
zone
和 cron
搭配使用,指定时区。通常为空串(默认值),会取本机设置的时区。
表示一次任务执行完之后隔多少时间再次执行该任务,是从任务执行完开始计时的,单位毫秒。fiexedDelayString
则支持数字字符串,这样可以方便将该值移至属性文件或者使用 java.time.Duration
来定义该值。
表示每隔多长时间就执行一次该任务,是从任务开始被执行时计时的,与任务耗时时长无关,单位毫秒。
表示间隔多长时间首次执行该任务
当SpringBoot启动之后,定时任务即开始按照
@Schedule
配置的规则开始执行。
有时接收到客户端请求后,在后台分发多个任务异步执行,而主线程先对客户端进行响应以提高用户体验。这时就涉及到了异步任务的调用。
设想一个场景:用户请求支付订单,这时应快速响应订单支付提交,而后台需要异步开启用户积分增加、减少商品库存、检测薅羊毛等一系列任务
1. 创建异步任务业务类如 MyAsyncTask
并添加 @Component
注解。
2. 在需要被异步调用的方法上添加 @Async
注解
package top.zhenganwen.springbootmybatis.task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class MyAsyncTask {
@Async
public void task1() {
long begin = System.currentTimeMillis();
//模拟增加用户积分
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("task1 spent:"+(end-begin));
}
@Async
public void task2() {
long begin = System.currentTimeMillis();
//模拟减少商品库存
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("task2 spent:"+(end-begin));
}
@Async
public void task3() {
long begin = System.currentTimeMillis();
//检测薅羊毛
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("task3 spent:"+(end-begin));
}
}
3. 在启动类上添加 @EnableAsync
注解以使组件中的 @Async
注解生效
4. 测试异步执行
package top.zhenganwen.springbootmybatis.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.zhenganwen.springbootmybatis.task.MyAsyncTask;
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private MyAsyncTask myAsyncTask;
@RequestMapping("submit")
public String submitOrder() {
System.out.println("用户请求支付订单===========");
long begin = System.currentTimeMillis();
myAsyncTask.task1();
myAsyncTask.task2();
myAsyncTask.task3();
long end = System.currentTimeMillis();
System.out.println("订单提交成功=====耗时:" + (end - begin));
return "success";
}
}
控制台输出如下:
用户请求支付订单===========
订单提交成功=====耗时:3
task1 spent:1001
task2 spent:2000
task3 spent:3001
用户请求支付订单===========
订单提交成功=====耗时:0
task1 spent:1000
task2 spent:2001
task3 spent:3000
5.测试同步执行,去掉启动类上的 @EnableAsync
重启后再次测试
用户请求支付订单===========
task1 spent:1001
task2 spent:2000
task3 spent:3001
订单提交成功=====耗时:6002
如果你想在异步执行一系列任务并获取任务执行结果后再响应该怎么办?JDK
并发包为我们提供了 Future
模式,实现了在主线程通过一个变量监控异步线程的执行状态从而在其执行完毕时获取执行结果。
1. 在异步任务执行完后返回一个 AyncResult
实例,用你想返回的数据构造该实例
package top.zhenganwen.springbootmybatis.task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
@Component
public class MyAsyncTask {
@Async
public Future task4() {
long begin = System.currentTimeMillis();
//模拟增加用户积分
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("task1 spent:"+(end-begin));
return new AsyncResult<>("task4");
}
@Async
public Future task5() {
long begin = System.currentTimeMillis();
//模拟减少商品库存
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("task2 spent:"+(end-begin));
return new AsyncResult<>("task4");
}
@Async
public Future task6() {
long begin = System.currentTimeMillis();
//检测薅羊毛
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("task3 spent:"+(end-begin));
return new AsyncResult<>("task4");
}
}
2. 在调用异步方法时获取异步结果变量 future
,通过该变量循环判断任务执行状态 isDone
并获取执行结果 get
,直接调用 get
是阻塞的。
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private MyAsyncTask myAsyncTask;
@RequestMapping("submit")
public String submitOrder() {
System.out.println("收到订单支付的请求");
long begin = System.currentTimeMillis();
Future task4 = myAsyncTask.task4();
Future task5 = myAsyncTask.task5();
Future task6 = myAsyncTask.task6();
while (true) {
if (task4.isDone() && task5.isDone() && task6.isDone()) {
long end = System.currentTimeMillis();
System.out.println("响应耗时:"+(end-begin));
break;
}
}
return "success";
}
}
控制台输出:
收到订单支付的请求
task1 spent:1001
task2 spent:2000
task3 spent:3000
响应耗时:3011
资料搜索:白玉搜一搜