spring mvc 异步调用 @Async

yujunyi_ 2018-08-03 10:35:01 3044 收藏 4

分类专栏: java

版权

定义

“异步调用”对应的是“同步调用”,同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;异步调用指程序在顺序执行时,不等待异步调用的语句返回结果就执行后面的程序。

同步调用

通过一个例子,来看下同步调用。 写了一个 TaskService ,里面有三个方法,分别模拟耗时2秒、3秒、4秒的操作。

@Service

public class TaskService {


public void doTaskOne() throws Exception {

System.out.println("开始做任务一");

long start = System.currentTimeMillis();

Thread.sleep(2000);

long end = System.currentTimeMillis();

System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");

}


public void doTaskTwo() throws Exception {

System.out.println("开始做任务二");

long start = System.currentTimeMillis();

Thread.sleep(3000);

long end = System.currentTimeMillis();

System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");

}


public void doTaskThree() throws Exception {

System.out.println("开始做任务三");

long start = System.currentTimeMillis();

Thread.sleep(4000);

long end = System.currentTimeMillis();

System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");

}


}

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

写一个 service ,调用这三个方法。

@Autowired

private TaskService task;


public String test() {

try {

task.doTaskOne();

task.doTaskTwo();

task.doTaskThree();

}catch (Exception e){

}

}

1

2

3

4

5

6

7

8

9

10

11

下面是运行结果,可以看到三个方法是依次执行的,分别耗时2秒、3秒、4秒、总耗时9秒。

14:38:13,846 INFO [stdout] (default task-8) 开始做任务一

14:38:15,848 INFO [stdout] (default task-8) 完成任务一,耗时:2001毫秒

14:38:15,850 INFO [stdout] (default task-8) 开始做任务二

14:38:18,850 INFO [stdout] (default task-8) 完成任务二,耗时:3000毫秒

14:38:18,852 INFO [stdout] (default task-8) 开始做任务三

14:38:22,853 INFO [stdout] (default task-8) 完成任务三,耗时:4001毫秒

1

2

3

4

5

6

异步调用

上面的同步调用,虽然顺利地完成了三个任务,但是执行时间比较长,如果这三个任务没有依赖关系,可以并发执行的话,可以考虑使用异步调用的方法。

在方法上加上 @Async 注解就能将同步函数变成异步函数,下面是更改后的代码:

@Service

public class TaskService {


@Async

public void doTaskOne() throws Exception {

System.out.println("开始做任务一");

long start = System.currentTimeMillis();

Thread.sleep(2000);

long end = System.currentTimeMillis();

System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");

}


@Async

public void doTaskTwo() throws Exception {

System.out.println("开始做任务二");

long start = System.currentTimeMillis();

Thread.sleep(3000);

long end = System.currentTimeMillis();

System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");

}


@Async

public void doTaskThree() throws Exception {

System.out.println("开始做任务三");

long start = System.currentTimeMillis();

Thread.sleep(4000);

long end = System.currentTimeMillis();

System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");

}


}

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

另外需要在application.xml中开启异步调用。

<task:annotation-driven executor="targetExecutor" scheduler="targetScheduler"/>


<task:executor id="targetExecutor" pool-size="5"/>

<task:scheduler id="targetScheduler" pool-size="10"/>

1

2

3

4

调用方法不用改动,运行后结果如下:

14:49:16,001 INFO [stdout] (targetExecutor-1) 开始做任务三

14:49:16,259 INFO [stdout] (targetExecutor-5) 开始做任务二

14:49:16,260 INFO [stdout] (targetExecutor-4) 开始做任务一

14:49:18,261 INFO [stdout] (targetExecutor-4) 完成任务一,耗时:2000毫秒

14:49:19,260 INFO [stdout] (targetExecutor-5) 完成任务二,耗时:3001毫秒

14:49:20,002 INFO [stdout] (targetExecutor-1) 完成任务三,耗时:4000毫秒

1

2

3

4

5

6

可以看到3个任务异步执行,总耗时4秒。

注意

@Async 所修饰的函数不要定义为 static 类型,这样异步调用不会生效。

调用方法和异步函数不能在一个 class 中。

异步回调

如果想知道异步函数什么时候执行完,那就需要使用 Future 来返回异步调用的结果。

改造后的代码如下:

@Service

public class TaskService {


@Async

public Future<String> doTaskOne() throws Exception {

System.out.println("开始做任务一");

long start = System.currentTimeMillis();

Thread.sleep(2000);

long end = System.currentTimeMillis();

System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");

return new AsyncResult<>("任务一完成");

}


@Async

public Future<String> doTaskTwo() throws Exception {

System.out.println("开始做任务二");

long start = System.currentTimeMillis();

Thread.sleep(3000);

long end = System.currentTimeMillis();

System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");

return new AsyncResult<>("任务二完成");

}


@Async

public Future<String> doTaskThree() throws Exception {

System.out.println("开始做任务三");

long start = System.currentTimeMillis();

Thread.sleep(4000);

long end = System.currentTimeMillis();

System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");

return new AsyncResult<>("任务三完成");

}


}

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

调用代码如下,增加了一个计算总耗时的:

long start = System.currentTimeMillis();


try {

Future<String> task1 = task.doTaskOne();

Future<String> task2 = task.doTaskTwo();

Future<String> task3 = task.doTaskThree();


while(true) {

if(task1.isDone() && task2.isDone() && task3.isDone()) {

// 三个任务都调用完成,退出循环等待

break;

}

Thread.sleep(100);

}


long end = System.currentTimeMillis();

System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");

}catch (Exception e){

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

运行结果如下:

15:21:05,676 INFO [stdout] (targetExecutor-4) 开始做任务三

15:21:05,676 INFO [stdout] (targetExecutor-3) 开始做任务二

15:21:05,677 INFO [stdout] (targetExecutor-2) 开始做任务一

15:21:07,678 INFO [stdout] (targetExecutor-2) 完成任务一,耗时:2001毫秒

15:21:08,677 INFO [stdout] (targetExecutor-3) 完成任务二,耗时:3000毫秒

15:21:09,677 INFO [stdout] (targetExecutor-4) 完成任务三,耗时:4001毫秒

15:21:09,704 INFO [stdout] (default task-21) 任务全部完成,总耗时:4036毫秒

1

2

3

4

5

6

7

总结

异步调用可以让一些和主要逻辑无关的代码异步执行,以提升性能。比如一些日志的代码、发送邮件短信等代码,可以使用异步执行。

————————————————