SpringBoot中@Async异步的使用及异步与同步的区别
简介
在开发过程中,异步是提升系统并发能力的一个重要利器。而 spring 中的 @Async 异步注解,使我们能够非常方便地实现方法地异步调用。接下来主要结合以下几个问题来讲述 java 程序中的异步的使用:
- 什么是同步
- 什么是异步,以及异步的作用
- 如何在 SpringBoot 中使用异步
1、什么是同步
同步调用,是遵循的顺序处理,一个一个调用
特征:在调用多个方法的时候,强调先后顺序,执行完一个方法再执行下一个方法,方法与方法之间不可调换先后顺序,不会并行执行
示例:
package com.example.demo.test1;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.concurrent.Future;
@Component
@Slf4j
public class SyncDemo {
public void test1() throws Exception {
log.info("test1开始执行");
Long start = System.currentTimeMillis();
Thread.sleep(1000);
Long end = System.currentTimeMillis();
log.info("test1执行结束");
log.info("test1执行时长:{}毫秒", end-start);
}
public void test2() throws Exception {
log.info("test2开始执行");
Long start = System.currentTimeMillis();
Thread.sleep(1500);
Long end = System.currentTimeMillis();
log.info("test2执行结束");
log.info("test2执行时长:{}毫秒", end-start);
}
public void test3() throws Exception {
log.info("test3开始执行");
Long start = System.currentTimeMillis();
Thread.sleep(2000);
Long end = System.currentTimeMillis();
log.info("test3执行结束");
log.info("test3执行时长:{}毫秒", end-start);
}
public static void main(String[] args) throws Exception {
SyncDemo syncDemo = new SyncDemo();
Long start = System.currentTimeMillis();
syncDemo.test1();
syncDemo.test2();
syncDemo.test3();
Long end = System.currentTimeMillis();
log.info("总执行时长:{}毫秒", end-start);
}
}
执行结果:
总执行时长,是test1(),test2(),test3() 三个方法的近似总和
2、什么是异步
异步主要是调用方法后,不用等方法执行完毕就直接返回。异步方法会与其他方法并行执行,以此来节约时间,提高程序的并发能力。
示例:
为准确统计所有异步方法执行时间,现在将方法的返回对象,改为 Future,以此来可以判断方法是否执行完毕
示例代码如下:
IAsyncDemoService.java
package com.example.demo.test1;
import java.util.concurrent.Future;
public interface IAsyncDemoService {
public Future<String> test1() throws Exception ;
public Future<String> test2() throws Exception ;
public Future<String> test3() throws Exception ;
}
AsyncDemoServiceImpl.java
package com.example.demo.test1;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import java.util.concurrent.Future;
@Service
@Slf4j
public class AsyncDemoServiceImpl implements IAsyncDemoService {
@Async
@Override
public Future<String> test1() throws Exception {
log.info("test1开始执行");
Long start = System.currentTimeMillis();
Thread.sleep(1000);
Long end = System.currentTimeMillis();
log.info("test1执行结束");
log.info("test1执行时长:{}毫秒", end-start);
return new AsyncResult<>("test1完成");
}
@Async
@Override
public Future<String> test2() throws Exception {
log.info("test2开始执行");
Long start = System.currentTimeMillis();
Thread.sleep(1500);
Long end = System.currentTimeMillis();
log.info("test2执行结束");
log.info("test2执行时长:{}毫秒", end-start);
return new AsyncResult<>("test2完成");
}
@Async
@Override
public Future<String> test3() throws Exception {
log.info("test3开始执行");
Long start = System.currentTimeMillis();
Thread.sleep(2000);
Long end = System.currentTimeMillis();
log.info("test3执行结束");
log.info("test3执行时长:{}毫秒", end-start);
return new AsyncResult<>("test3完成");
}
}
DemoApplicationTests.java
package com.example.demo;
import com.example.demo.test1.IAsyncDemoService;
import lombok.extern.slf4j.Slf4j;
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
@Slf4j
class DemoApplicationTests {
@Autowired
IAsyncDemoService asyncDemoService;
@Test
void contextLoads() throws Exception{
Long start = System.currentTimeMillis();
Future task1 = asyncDemoService.test1();
Future task2 = asyncDemoService.test2();
Future task3 = asyncDemoService.test3();
while (true) {
if (task1.isDone() && task2.isDone() && task3.isDone()) {
break;
}
}
Long end = System.currentTimeMillis();
log.info("总执行时长:{}毫秒", end-start);
}
}
执行结果如下:总执行时间与执行最长的 test3()方法的时间相近
结论:异步方法是通过形式,调用后即返回,具体方法的执行,不影响其他方法的执行,多个方法并行的方式实现。
3、@Async 失效的情况
在使用 注解@Async 的时候,一定要注意规避以下情况,会造成 注解失效的情况
- 注解@Async的方法不是public方法
- 注解@Async的返回值只能为void或Future
- 注解@Async方法使用static修饰也会失效
- spring无法扫描到异步类,没加注解@Async或@EnableAsync注解
- 调用方与被调用方不能在同一个类
- 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
- 在Async方法上标注@Transactional是没用的.但在Async方法调用的方法上标注@Transcational是有效的
4、异步线程池的设置
使用 @Async 来实现异步,默认使用Spring创建ThreadPoolTaskExecutor,默认线程池的参数设置为:
- 默认心线程数:8,
- 最大线程数:Integet.MAX_VALUE,
- 队列使用LinkedBlockingQueue,
- 容量是:Integet.MAX_VALUE,
- 空闲线程保留时间:60s,
- 线程池拒绝策略:AbortPolicy。
问题:_
_并发情况下,会无限创建线程,导致系统资源耗尽,所以我们要手动设置线程池的配置,来避免无限创建线程的情况。
配置如下:
application.yaml
spring:
task:
execution:
pool:
max-size: 6
core-size: 3
keep-alive: 3s
queue-capacity: 1000
自定义线程池配置类:
ExecutorConfig.java
@Configuration
@Data
public class ExecutorConfig{
/**
* 核心线程
*/
private int corePoolSize;
/**
* 最大线程
*/
private int maxPoolSize;
/**
* 队列容量
*/
private int queueCapacity;
/**
* 保持时间
*/
private int keepAliveSeconds;
/**
* 名称前缀
*/
private String preFix;
@Bean
public Executor myExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setThreadNamePrefix(preFix);
executor.setRejectedExecutionHandler( new ThreadPoolExecutor.AbortPolicy());
executor.initialize();
return executor;
}
}
使用方式:
在使用 @Async 的时候,指定线程池为自定义线程池 myExecutor
例如:
@Async("myExecutor")
public void test1() throws Exception {
}