引言: 在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的;但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务
在解释异步调用之前,我们先来看同步调用的定义;同步就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果。 异步调用则是只是发送了调用的指令,调用者无需等待被调用的方法完全执行完毕;而是继续执行下面的流程。
例如, 在某个调用中,需要顺序调用 A, B, C三个过程方法;如他们都是同步调用,则需要将他们都顺序执行完毕之后,方算作过程执行完毕; 如B为一个异步的调用方法,则在执行完A之后,调用B,并不等待B完成,而是执行开始调用C,待C执行完毕之后,就意味着这个过程执行完毕了。
在Java中,一般在处理类似的场景之时,都是基于创建独立的线程去完成相应的异步调用逻辑,通过主线程和不同的线程之间的执行流程,从而在启动独立的线程之后,主线程继续执行而不会产生停滞等待的情况。
而spring boot 种提供了更加简单的方式不用我们自己去创建了,只需要很简单的在方法上添加一个@Async注解那么就可以实现异步了,这种方式的原理是基于AOP思想…下面就来学习基于@Async注解的方式
在Spring中,基于@Async标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。
基于SpringBoot配置的启用方式:
@Configuration
public class MyAsyncConfigurer implements AsyncConfigurer {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
//最大线程数30
executor.setMaxPoolSize(30);
//缓冲队列200:用来缓冲执行任务的队列
executor.setQueueCapacity(200);
// 允许线程的空闲时间60秒:超过了核心线程数之外的线程,在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("taskExecutor-");
//设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
executor.setWaitForTasksToCompleteOnShutdown(true);
//设置线程池中任务的等待时间
executor.setAwaitTerminationSeconds(60);
//当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
然后在启动类上添加
@EnableAsync
没有在启动类上添加@EnableAsync
1.
因为aop代理的缘故,被调用方法和调用处的代码都处在同一个类,所以只是相当于本类调用,并没有使用代理类 从而@Async并没有产生效果。
1.
异步方法使用注解@Async的返回值只能为void或者Future。
1.
@Async注解的方法必须是public方法。
接口
@GetMapping("/testAsync1")
@Async
public ResponseEntity<String> testAsync1(@PathVariable String string) {
System.out.println("接口执行开始===========================");
testAsync.asyncMethodWithVoidReturnType(); //需要异步的方法
System.out.println("这个接口执行完毕=========================");
return ResponseEntity.ok("这个接口执行完毕=========================");
}
异步方法
@Component
public class TestAsync {
@Async("taskExecutor") //标注使用异步
public void asyncMethodWithVoidReturnType() {
System.out.println("异步方法开始");
try {
Thread.sleep(5000);//5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步方法结束");
}
}
控制台执行效果:
接口执行开始===========================
这个接口执行完毕=========================
异步方法开始
异步方法结束
使用的方式非常简单,一个标注即可解决所有的问题。
这种方式一般用的比较少,用的最多的就是提升同步接口下响应的速度,
比如: 有些添加数据时候前端返回一个大的对象JSON, 里边包含很多小的对象,而添加的时候都是分开添加的互不影响,这样就可以使用异步操作,然后在最后进行监控如果都成功了那么,在响应给前端,随着业务的复杂度提升-速度可以提升n倍
接口
@Autowired
private TestAsync testAsync;
//http://localhost:10101/user/testAsync
@GetMapping("/testAsync2")
public ResponseEntity<String> testAsync2(){
long startTime = System.currentTimeMillis(); //获取开始时间
System.out.println("接口方法执行开始");
Future<String> future = testAsync.asyncMethodWithReturnType(); //异步执行
try {
System.out.println("其他代码执行过程时间");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Future<String> future1 = testAsync.asyncMethodWithReturnType1();//异步执行
try {
System.out.println("其他代码执行过程时间1");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean a=false,b=false; //创建2个异步方法的控制变量
while (true) { // 判断异步方法执行 时候成功
System.out.println("监测是否都执行完毕-------------------------");
if (future.isDone()&&!a) { //判断异步方法是否执行完毕
System.out.println("异步方法1执行完毕-开始后期处理");
a=true;
}
if (future1.isDone()&&!b) { //判断异步方法是否执行完毕
System.out.println("异步方法2执行完毕-开始后期处理");
b=true;
}
if (a&&b) { //如果都执行成功的话那么 跳出循环方法直接结束 ,用户的响应结束
System.out.println("异步方法1和2都执行完毕-开始后期处理");
break;
}
try {
Thread.sleep(500); //500毫秒监测一次,不然消耗CPU资源太多了
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("接口方法执行结束");
long endTime = System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间:" + (endTime - startTime) + "ms"); //输出程序运行时间
return ResponseEntity.ok("方法执行结束");
}
异步方法
@Component
public class TestAsync {
@Async("taskExecutor")
public Future<String> asyncMethodWithReturnType() {
System.out.println("异步方法开始");
try {
Thread.sleep(5000); //执行时间5秒
//返回执行完成后的内容
return new AsyncResult<String>("hello world !!!!");
} catch (InterruptedException e) {
e.printStackTrace();
return new AsyncResult<String>("异步方法-出现问题-结束");
}
}
@Async("taskExecutor")
public Future<String> asyncMethodWithReturnType1() {
System.out.println("异步方法开始");
try {
Thread.sleep(5000); //执行时间5秒
//返回执行完成后的内容
return new AsyncResult<String>("hello world !!!!");
} catch (InterruptedException e) {
e.printStackTrace();
return new AsyncResult<String>("异步方法-出现问题-结束");
}
}
执行效果:
接口方法执行开始
其他代码执行过程时间
异步方法开始
其他代码执行过程时间1
异步方法开始
监测是否都执行完毕-------------------------
监测是否都执行完毕-------------------------
监测是否都执行完毕-------------------------
监测是否都执行完毕-------------------------
异步方法1执行完毕-开始后期处理
监测是否都执行完毕-------------------------
监测是否都执行完毕-------------------------
监测是否都执行完毕-------------------------
异步方法2执行完毕-开始后期处理
异步方法1和2都执行完毕-开始后期处理
接口方法执行结束
程序运行时间:7026ms
然后我们在试试不使用@Async的方法看看执行时间是多少
测试代码如下:
@GetMapping("/testAsync3") // 12036ms
public ResponseEntity<String> testAsync3(){
long startTime = System.currentTimeMillis(); //获取开始时间
System.out.println("接口方法开始");
try {
System.out.println("方法1开始");
Thread.sleep(5000); //执行时间
System.out.println("方法1结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("其他代码执行过程时间");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("方法2开始");
Thread.sleep(5000); //执行时间
System.out.println("方法2结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("其他代码执行过程时间1");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("接口方法执行结束");
long endTime = System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间:" + (endTime - startTime) + "ms"); //输出程序运行时间
return ResponseEntity.ok("方法执行结束");
}
接口方法开始
异步方法开始
异步方法结束
其他代码执行过程时间
异步1方法开始
异步1方法结束
其他代码执行过程时间1
接口方法执行结束
程序运行时间:14024ms
可以看到这个执行时间将近提上了一倍以上,这还是把执行时间写死了,如果不写死差距会更大
这种方式是直接让用户请求接口时候直接就能响应,成功
,几乎0延迟的方式,
这种方式一般用于 不对数据进行增删改的操作时候使用,
一般都是,用于用户并不需要知道是否执行成功, 前端调用接口后直接默认返回操作成功就行,
急需要用户体验的场景时候,可以使用这种方式.
可能会问了,这种方式和基于@Async无返回值调用有啥区别啊,
@Async无返回值调用
这样就会导致接口可能还是无法做到秒响应的效果
一般需要秒响应的场景,不是很多,如果甲方很变态的话,那么也没办法
接口
@GetMapping("/testAsync4")
@Async("taskExecutor")
public void testAsync4(){
try {
System.out.println("其他代码执行过程时间1");
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行完毕");
}
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/weixin_45203607/article/details/120248659
内容来源于网络,如有侵权,请联系作者删除!