0%

SpringBoot2.x(十)整合定时任务和异步任务处理

整合定时任务

  1. 创建定时任务业务类,如 MyTask
  2. MyTask上添加 Component注解以作为组件能被扫描到。
  3. 在定时任务方法上添加 @Scheduled以指明执行该任务的时机
1
2
3
4
5
6
7
8
9
10
11
12
13
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());
}
}
  1. 在启动类上添加 @EnableScheduling以使组件中的 @Scheduled注解生效。

@Scheduled

定时任务的核心就在于任务执行的时机,即 @Scheduled注解的使用。该注解的属性有 cronzonefixedDelayfixedDelayStringfixedRatefixedRateStringinitialDelayinitialDelayString

cron&zone

cron表达式是一种定位时间点的表达式。如 @Scheduled(cron="*/1 * * * * *")表示每秒,其用法大家自行查阅。

cron表达式在线生成工具: https://tool.lu/crontab/

zonecron搭配使用,指定时区。通常为空串(默认值),会取本机设置的时区。

fixedDelay

表示一次任务执行完之后隔多少时间再次执行该任务,是从任务执行完开始计时的,单位毫秒fiexedDelayString则支持数字字符串,这样可以方便将该值移至属性文件或者使用 java.time.Duration来定义该值。

fixedRate

表示每隔多长时间就执行一次该任务,是从任务开始被执行时计时的,与任务耗时时长无关,单位毫秒

initialDelay

表示间隔多长时间首次执行该任务

当SpringBoot启动之后,定时任务即开始按照 @Schedule配置的规则开始执行。


异步任务处理

有时接收到客户端请求后,在后台分发多个任务异步执行,而主线程先对客户端进行响应以提高用户体验。这时就涉及到了异步任务的调用。

设想一个场景:用户请求支付订单,这时应快速响应订单支付提交,而后台需要异步开启用户积分增加、减少商品库存、检测薅羊毛等一系列任务

  1. 创建异步任务业务类如 MyAsyncTask并添加 @Component注解。
  2. 在需要被异步调用的方法上添加 @Async注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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));
}
}
  1. 在启动类上添加 @EnableAsync注解以使组件中的 @Async注解生效
  2. 测试异步执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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";
}
}

控制台输出如下:

1
2
3
4
5
6
7
8
9
10
11
用户请求支付订单===========
订单提交成功=====耗时:3
task1 spent:1001
task2 spent:2000
task3 spent:3001

用户请求支付订单===========
订单提交成功=====耗时:0
task1 spent:1000
task2 spent:2001
task3 spent:3000

5.测试同步执行,去掉启动类上的 @EnableAsync重启后再次测试

1
2
3
4
5
用户请求支付订单===========
task1 spent:1001
task2 spent:2000
task3 spent:3001
订单提交成功=====耗时:6002

Future

如果你想在异步执行一系列任务并获取任务执行结果后再响应该怎么办?JDK并发包为我们提供了 Future模式,实现了在主线程通过一个变量监控异步线程的执行状态从而在其执行完毕时获取执行结果。

  1. 在异步任务执行完后返回一个 AyncResult实例,用你想返回的数据构造该实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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<String> 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<String> 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<String> 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");
}
}
  1. 在调用异步方法时获取异步结果变量 future,通过该变量循环判断任务执行状态 isDone并获取执行结果 get直接调用 get是阻塞的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
@RequestMapping("order")
public class OrderController {

@Autowired
private MyAsyncTask myAsyncTask;
@RequestMapping("submit")
public String submitOrder() {

System.out.println("收到订单支付的请求");
long begin = System.currentTimeMillis();
Future<String> task4 = myAsyncTask.task4();
Future<String> task5 = myAsyncTask.task5();
Future<String> task6 = myAsyncTask.task6();
while (true) {
if (task4.isDone() && task5.isDone() && task6.isDone()) {
long end = System.currentTimeMillis();
System.out.println("响应耗时:"+(end-begin));
break;
}
}
return "success";
}
}

控制台输出:

1
2
3
4
5
收到订单支付的请求
task1 spent:1001
task2 spent:2000
task3 spent:3000
响应耗时:3011
鼓励一下~