目录
一、定义
1、同步调用
2、异步调用
二、示例
1、同步调用
执行类:
测试用例:
运行结果:
2、异步调用
(1)普通调用
执行类:
测试用例:
运行结果:
(2)异步回调
执行类:
测试用例:
执行逻辑:
一、定义
1、同步调用
- 定义:同步调用是一种按照顺序依次执行任务的方式。在程序中,当一个函数或方法调用另一个函数或方法时,调用者会暂停自身的执行,等待被调用者执行完毕并返回结果后,调用者才会继续执行后续的操作。
- 就好像一个人在排队等待服务,必须等到前面的人完成服务后,自己才能接受服务并继续下一步行动。
2、异步调用
- 定义:异步调用是一种不阻塞调用者执行的调用方式。当一个函数或方法被异步调用时,调用者不会等待被调用者执行完毕,而是继续执行自己后续的操作。被调用者在后台独立运行,当它完成任务后,会通过某种方式(如回调函数、事件通知等)将结果返回给调用者。
- 这就好比一个人在餐厅点了菜后,不需要一直坐在那里等待上菜,而是可以去做其他事情,等菜做好了,服务员会通知他。
二、示例
1、同步调用
执行类:
定义SynchronizationTask类,创建三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10秒内)
package com.example.springbootasync.demos.web;import org.springframework.stereotype.Component;import java.util.Random;/*** @Author: * @CreateTime: 2024-11-15* @Description: 同步任务执行类*/
@Component
public class SynchronizationTask {public static Random random = new Random();public void taskOne() throws Exception {System.out.println("任务一开始执行......");long start = System.currentTimeMillis();Thread.sleep(random.nextInt(10000));long end = System.currentTimeMillis();System.out.println("任务一完成耗时:" + (end - start) + "毫秒");}public void taskTwo() throws Exception {System.out.println("任务二开始执行......");long start = System.currentTimeMillis();Thread.sleep(random.nextInt(10000));long end = System.currentTimeMillis();System.out.println("任务二完成耗时:" + (end - start) + "毫秒");}public void taskThree() throws Exception {System.out.println("任务三开始执行......");long start = System.currentTimeMillis();Thread.sleep(random.nextInt(10000));long end = System.currentTimeMillis();System.out.println("任务三完成耗时:" + (end - start) + "毫秒");}}
测试用例:
在单元测试用例中,注入SynchronizationTask对象,并在测试用例中执行taskOne、taskTwo、taskThree三个函数。
package com.example.springbootasync;import com.example.springbootasync.demos.web.SynchronizationTask;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringBootAsyncApplicationTests {@Autowiredprivate SynchronizationTask sTask;@Testvoid synchronizationTask() throws Exception {sTask.taskOne();sTask.taskTwo();sTask.taskThree();}}
执行单元测试
运行结果:
任务一开始执行......
任务一完成耗时:2649毫秒
任务二开始执行......
任务二完成耗时:7083毫秒
任务三开始执行......
任务三完成耗时:5752毫秒
可以看出三个任务是按照顺序执行的,这就是同步调用,一个任务执行完成才能执行下一个任务。
2、异步调用
上述的同步调用虽然顺利的执行完了三个任务,但是可以看到执行时间比较长,若这三个任务本身之间不存在依赖关系,可以并发执行的话,同步调用在执行效率方面就比较差,可以考虑通过异步调用的方式来并发执行。
为了让@Async
注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsync
,如下所示:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;@SpringBootApplication
@EnableAsync
public class SpringBootAsyncApplication {public static void main(String[] args) {SpringApplication.run(SpringBootAsyncApplication.class, args);}}
(1)普通调用
在Spring Boot中,我们只需要通过使用 @Async注解 就能简单的将原来的 同步函数变为异步函数,将SynchronizationTask类进行改造,如下:
执行类:
package com.example.springbootasync.demos.web;import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;import java.util.Random;
import java.util.concurrent.Future;/*** @Author: wxzhanglinsen* @CreateTime: 2024-11-15* @Description: 异步任务执行类*/
@Component
public class AsyncTask {public static Random random = new Random();@Asyncpublic void taskOne() throws Exception {//同上}@Asyncpublic void taskTwo() throws Exception {//同上}@Asyncpublic void taskThree() throws Exception {//同上}}
测试用例:
package com.example.springbootasync;import com.example.springbootasync.demos.web.AsyncTask;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringBootAsyncApplicationTests {@Autowiredprivate AsyncTask aTask;@Testvoid asyncTask() throws Exception {aTask.taskOne();aTask.taskTwo();aTask.taskThree();}}
运行结果:
任务二开始执行......
任务三开始执行......
任务一开始执行......
任务二开始执行......
任务一开始执行......
任务三开始执行......
此时可以反复执行单元测试,您可能会遇到各种不同的结果,比如:
没有任何任务相关的输出
有部分任务相关的输出
乱序的任务相关的输出
原因是目前taskOne、taskTwo、taskThree三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的情况。
注意:@Async所修饰的函数不要定义为static类型,这样异步调用不会生效。
(2)异步回调
为了让taskOne、taskTwo、taskThree能正常结束,假设我们需要统计一下三个任务并发执行共耗时多少,这就需要等到上述三个函数都完成调动之后记录时间,并计算结果。
那么我们如何判断上述三个异步调用是否已经执行完成呢?我们需要使用Future来返回异步调用的结果,就像如下方式改造AsyncTask类中的函数:
执行类:
package com.example.springbootasync.demos.web;import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;import java.util.Random;
import java.util.concurrent.Future;/*** @Author: wxzhanglinsen* @CreateTime: 2024-11-15* @Description: 异步任务执行类*/
@Component
public class AsyncTask {public static Random random = new Random();@Asyncpublic Future<String> taskOne() throws Exception {System.out.println("任务一开始执行......");long start = System.currentTimeMillis();Thread.sleep(random.nextInt(10000));long end = System.currentTimeMillis();System.out.println("任务一完成耗时:" + (end - start) + "毫秒");return new AsyncResult<>("任务一完成");}@Asyncpublic Future<String> taskTwo() throws Exception {System.out.println("任务二开始执行......");long start = System.currentTimeMillis();Thread.sleep(random.nextInt(10000));long end = System.currentTimeMillis();System.out.println("任务二完成耗时:" + (end - start) + "毫秒");return new AsyncResult<>("任务二完成");}@Asyncpublic Future<String> taskThree() throws Exception {System.out.println("任务三开始执行......");long start = System.currentTimeMillis();Thread.sleep(random.nextInt(10000));long end = System.currentTimeMillis();System.out.println("任务三完成耗时:" + (end - start) + "毫秒");return new AsyncResult<>("任务三完成");}
}
测试用例:
package com.example.springbootasync;import com.example.springbootasync.demos.web.AsyncTask;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.concurrent.Future;@SpringBootTest
class SpringBootAsyncApplicationTests {@Autowiredprivate AsyncTask aTask;@Testvoid asyncTask() throws Exception {long start = System.currentTimeMillis();Future<String> task1 = aTask.taskOne();Future<String> task2 = aTask.taskTwo();Future<String> task3 = aTask.taskThree();while(true) {if(task1.isDone() && task2.isDone() && task3.isDone()) {// 三个任务都调用完成,退出循环等待break;}//停隔1s再进行扫描,扫描任务是否都已经完成Thread.sleep(1000);}long end = System.currentTimeMillis();System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");}
}
执行逻辑:
在测试用例一开始记录开始时间
在调用三个异步函数的时候,返回Future类型的结果对象
在调用完三个异步函数之后,开启一个循环,根据返回的Future对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断。
跳出循环之后,根据
结束时间 - 开始时间
,计算出三个任务并发执行的总耗时。
运行结果:
任务一开始执行......
任务二开始执行......
任务三开始执行......
任务一完成耗时:188毫秒
任务二完成耗时:4828毫秒
任务三完成耗时:9208毫秒
任务全部完成,总耗时:10014毫秒
可以看到,通过异步调用,让任务一、二、三并发执行,有效的减少了程序的总运行时间。