SpringBoot应用中需要添加@EnableAsync注解,来开启异步调用,一般还会配置一个线程池,异步的方法交给特定的线程池完成,
1. 配置线程池
添加一个Configuration类, 代码如下:
@Configuration
@EnableAsync
public class ThreadConfig {
@Bean("doSomethingExecutor")
public Executor doSomethingExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数:线程池创建时候初始化的线程数
executor.setCorePoolSize(30);
// 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(60);
// 缓冲队列:用来缓冲执行任务的队列
executor.setQueueCapacity(500);
// 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
// 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("do-something-");
// 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
executor.initialize();
return executor;
}
}
ThreadPoolTaskExecutor线程是Spring的线程池,其底层是依据JDK线程池ThreadPoolExecutor来实现的, 其内部常用的变量状态有 :
监控线程池状态
常用状态:
taskCount:线程需要执行的任务个数。
completedTaskCount:线程池在运行过程中已完成的任务数。
largestPoolSize:线程池曾经创建过的最大线程数量。
getPoolSize获取当前线程池的线程数量。
getActiveCount:获取活动的线程的数量
通过继承线程池,重写beforeExecute,afterExecute和terminated方法来在线程执行任务前,线程执行任务结束,和线程终结前获取线程的运行情况,根据具体情况调整线程池的线程数量。
注意: 有些状态在 ThreadPoolTaskExecutor 可能获取不到, 可以先获取 ThreadPoolExecutor , 再获取其状态, 示例代码如下:
ThreadPoolTaskExecutor threadPoolTaskExecutor=(ThreadPoolTaskExecutor)executor;
System.out.println(threadPoolTaskExecutor.getThreadPoolExecutor().getCompletedTaskCount());
2. 在方法上开启异步调用方法
使用的方式非常简单,在需要异步的方法上加@Async注解, 创建相应的多线程异步方法类, 如下:
@Slf4j
@Service
public class AsyncService {
// 指定使用beanname为doSomethingExecutor的线程池
@Async("doSomethingExecutor")
public String doSomething(String message) {
log.info("do something, message={}", message);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.error("do something error: ", e);
}
return message;
}
}
3. 调用该异步方法
这里使用 一个 controller来启动调用, 代码如下:
@RestController
public class AsyncController {
@Autowired
private AsyncService asyncService;
@GetMapping("/open/something")
public String something() {
int count = 10;
for (int i = 0; i < count; i++) {
asyncService.doSomething("index = " + i);
}
return "success";
}
}
启动工程, 访问:localhost:8080/open/something,输出如下:
由此可见已经达到异步执行的效果了,并且使用到了刚刚自定义配置的线程池。
4. 获取异步方法返回值
当异步方法有返回值时,如何获取异步方法执行的返回结果呢?这时需要异步调用的方法带有返回值CompletableFuture。
CompletableFuture是对Feature的增强,Feature只能处理简单的异步任务,而CompletableFuture可以将多个异步任务进行复杂的组合。如下:
在AsyncService类中添加异步方法, 返回值类型为 CompletableFuture
@Slf4j
@Service
public class AsyncService {
@Resource
@Qualifier("doSomethingExecutor")
private Executor executor;
// 指定使用 beanName 为 doSomethingExecutor 的线程池
@Async("doSomethingExecutor")
public String doSomething(String message) {
//
log.info("{}, message={}", Thread.currentThread().getName(), message);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.error("{} error: ", Thread.currentThread().getName(), e);
}
return message;
}
@Async("doSomethingExecutor")
public CompletableFuture<String> doSomethingComp(String message) throws InterruptedException {
log.info("do something1: {}", message);
Thread.sleep(1000);
return CompletableFuture.completedFuture("do something1: " + message);
}
}
再在 AsyncController 类中开启调用 :
@RestController
public class AsyncController {
@Autowired
private AsyncService asyncService;
@GetMapping("/open/something")
public String something() {
int count = 10;
for (int i = 0; i < count; i++) {
asyncService.doSomething("index = " + i);
}
return "success";
}
@SneakyThrows
@GetMapping("/open/somethingComp")
public String somethingComp() {
int count = 10;
CompletableFuture[] futures = new CompletableFuture[count];
// 开启待返回值得异步方法
for (int i = 0; i < count; i++) {
futures[i] = asyncService.doSomethingComp("index = " + i);
}
try {// 等待所有任务都执行完
CompletableFuture.allOf(futures).join();
} catch (Exception e) {
System.out.println("CompletableFuture error");
}
System.out.println("Get all return value! ");
return "success";
}
}
然后 访问该接口 :http://localhost:8080/open/somethingComp ,输出如下:
可以看到使用CompletableFuture.join 方法 等待所有异步方法执行完成后才执行下面的语句.
5. 注意事项
@Async注解会在以下几个场景失效,也就是说明明使用了@Async注解,但就没有走多线程。
- 异步方法使用static关键词修饰;
- 异步类不是一个Spring容器的bean(一般使用注解@Component和@Service,并且能被Spring扫描到);
- SpringBoot应用中没有添加@EnableAsync注解;
- 在同一个类中,一个方法调用另外一个有@Async注解的方法,注解不会生效。原因是@Async注解的方法,是在代理类中执行的。
其他:
异步方法使用注解@Async的返回值只能为void或者Future及其子类,当返回结果为其他类型时,方法还是会异步执行,但是返回值都是null
参考: Spring Boot如何优雅的使用多线程实例详解
CompletableFuture等待所有任务结束
Spring线程池ThreadPoolTaskExecutor学习总结