spring mvc 异步调用 @Async

定义

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

同步调用

通过一个例子,来看下同步调用。 写了一个 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) + "毫秒");
        }
    
    }

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

    @Autowired
    private TaskService task;
    
    public String test() {
        try {
            task.doTaskOne();
            task.doTaskTwo();
            task.doTaskThree();
        }catch (Exception e){
        }
    }

下面是运行结果,可以看到三个方法是依次执行的,分别耗时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毫秒

异步调用

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

在方法上加上 @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) + "毫秒");
        }
    
    }

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

    <task:annotation-driven executor="targetExecutor" scheduler="targetScheduler"/>
    
    <task:executor id="targetExecutor" pool-size="5"/>
    <task:scheduler id="targetScheduler" pool-size="10"/>

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

    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毫秒

可以看到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<>("任务三完成");
        }
    
    }

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

    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){
    }

运行结果如下:

    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毫秒

总结

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

你可能感兴趣的:(java)