SpringBoot异步调用
一、概述
在程序执行时候还有一个瓶颈,串行执行,可以通过使用不同线程类快速提升应用的速度。
要启用Spring的异步功能,必须要使用@EnableAsync注解。这样将会透明地使用java.util.concurrent.Executor来执行所有带有@Async注解的方法。@Async所修饰的函数不要定义为static类型,这样异步调用不会生效。
针对调用的Async,如果不做Future特殊处理,执行完调用方法会立即返回结果,如异步邮件发送,不会真的等邮件发送完毕才响应客户,如需等待可以使用Future阻塞处理。
二、异步实现 - EnableAsync
2.1 核心注解
在SpringBoot中将一个方法声明为异步方法非常简单,只需两个注解即可@EnableAsync和@Async。
- @EnableAsync:用于开启
SpringBoot支持异步的功能,用在SpringBoot的启动类上。 - @Async:用于方法上,标记该方法为异步处理方法。
需要注意的是@Async并不支持用于被@Configuration注解的类的方法上。同一个类中,一个方法调用另外一个有@Async的方法,注解也是不会生效的。
2.2 使用示例
1、开启异步功能
在main方法增加@EnableAsync注解
@SpringBootApplication
@EnableAsync
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
System.out.println("ThreadId:" + Thread.currentThread().getId());
}
}
2、异步方法
在所需方法增加@Async注解
@Component
public class Task {
@Async
public void doTaskOne() throws Exception {
for (int i = 0; i < 3; i++) {
Thread.sleep(200);
System.out.println("ThreadId:" + Thread.currentThread().getId() + ":doTaskOne");
}
}
@Async
public void doTaskTwo() throws Exception {
for (int i = 0; i < 3; i++) {
Thread.sleep(200);
System.out.println("ThreadId:" + Thread.currentThread().getId() + ":doTaskTwo");
}
}
@Async
public void doTaskThree() throws Exception {
for (int i = 0; i < 3; i++) {
Thread.sleep(200);
System.out.println("ThreadId:" + Thread.currentThread().getId() + ":doTaskThree");
}
}
}
@Async注解的使用与Callable有类似之处,在默认情况下使用的都是SimpleAsyncTaskExecutor线程池,可参考Callable中的方式来自定义线程池。
3、查看调用
@RestController
@RequiredArgsConstructor
public class TestAsyns {
private final Task task;
@RequestMapping("/testAsync")
public ResponseEntity testAsync() throws Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
return ResponseEntity.ok("ok");
}
}
上述方法依次调用三个方法。
如果去除@EnableAsync注解,输出如下:【可见是串行执行】
ThreadId:33:doTaskOne
ThreadId:33:doTaskOne
ThreadId:33:doTaskOne
ThreadId:33:doTaskTwo
ThreadId:33:doTaskTwo
ThreadId:33:doTaskTwo
ThreadId:33:doTaskThree
ThreadId:33:doTaskThree
ThreadId:33:doTaskThree
如果增加@EnableAsync注解,输出如下:【可见是并行执行】
ThreadId:56:doTaskThree
ThreadId:55:doTaskTwo
ThreadId:54:doTaskOne
ThreadId:54:doTaskOne
ThreadId:55:doTaskTwo
ThreadId:56:doTaskThree
ThreadId:54:doTaskOne
ThreadId:56:doTaskThree
ThreadId:55:doTaskTwo
三、自定义异步线程池
Spring默认使用SimpleAsyncTaskExecutor执行异步任务,该线程池不重用线程(每次调用创建新线程),在高并发场景下会导致线程泛滥,严重影响系统性能。因此,生产环境必须自定义线程池,通过合理的线程池参数配置,实现资源的高效利用。
3.1 线程池核心参数
在配置线程池前,需理解核心参数的含义:
- corePoolSize:核心线程数,线程池维护的最小线程数量,即使线程空闲也不会销毁
- maxPoolSize:最大线程数,线程池可创建的最大线程数量
- queueCapacity:任务队列容量,核心线程满时,任务会先进入队列等待
- keepAliveSeconds:非核心线程空闲存活时间,超过该时间会被销毁
- threadNamePrefix:线程名称前缀,便于日志排查
- rejectedExecutionHandler:任务拒绝策略,当线程池和队列都满时的处理方式
3.2 实现方式
3.2.1 注入Bean方式
适用于简单场景,通过@Bean注解创建线程池实例:
import java.util.concurrent.Executor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
public class ThreadConfig {
// 执行需要依赖线程池,这里就来配置一个线程池
@Bean
public Executor getExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.initialize();
return executor;
}
}
3.2.2 实现AsyncConfigurer接口
适用于复杂场景,可同时配置线程池和异常处理器:
@Configuration
@Slf4j
public class AsyncConfiguration implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
//做好不超过10个,这里写两个方便测试
return Executors.newFixedThreadPool(2);
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex,method,params) -> log.error("Uncaught async error", ex);
}
}
Executor的初始化配置,还有很多种,可以参看https://www.cnblogs.com/bjlhx/category/1086008.html
3.3 Spring内置线程池
Spring已经实现的异步线程池:
SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。ConcurrentTaskExecutor(不推荐):Executor的适配类。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。- ThreadPoolTaskExecutor(推荐):最常使用,其实质是对java.util.concurrent.ThreadPoolExecutor的包装。
使用上述配置,能够确保在应用中,用来处理异步任务的线程不会超过10个。这对于web应用很重要,因为每个客户端都会有一个专用的线程。你所使用的线程越多,阻塞时间越长那么能够处理的客户端就会越少。
如果设置成两个,程序中有3个异步线程,也会只有两个运行,如下:
ThreadId:55:doTaskTwo
ThreadId:54:doTaskOne
ThreadId:55:doTaskTwo
ThreadId:54:doTaskOne
ThreadId:55:doTaskTwo
ThreadId:54:doTaskOne
ThreadId:55:doTaskThree
ThreadId:55:doTaskThree
ThreadId:55:doTaskThree
四、异步结果处理
4.1 Callable
Callable是Java并发包中的接口,用于返回异步执行结果,适用于简单的异步结果获取场景。
基于Callable的处理流程如下:
Spring MVC开启副线程处理业务(将Callable提交到TaskExecutor);DispatcherServlet和所有的Filter退出Web容器的线程,但是response保持打开状态;Callable返回结果,SpringMVC将原始请求重新派发给容器(再重新请求一次/email),恢复之前的处理;DispatcherServlet重新被调用,将结果返回给用户;
代码实现示例如下:
@GetMapping("/email")
public Callable<String> order() {
System.out.println("主线程开始:" + Thread.currentThread().getName());
Callable<String> result = () -> {
System.out.println("副线程开始:" + Thread.currentThread().getName());
Thread.sleep(1000);
System.out.println("副线程返回:" + Thread.currentThread().getName());
return "success";
};
System.out.println("主线程返回:" + Thread.currentThread().getName());
return result;
}
访问对应URL,控制台输入日志如下:
主线程开始:http-nio-8080-exec-1
主线程返回:http-nio-8080-exec-1
副线程开始:task-1
副线程返回:task-1
通过日志可以看出,主线程已经完成了,副线程才进行执行。同时,URL返回结果“success”。这也说明一个问题,服务器端的异步处理对客户端来说是不可见的。
Callable默认使用SimpleAsyncTaskExecutor类来执行,这个类非常简单而且没有重用线程。在实践中,需要使用AsyncTaskExecutor类来对线程进行配置。
这里通过实现WebMvcConfigurer接口来完成线程池的配置。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;
/**
* 配置线程池
*/
@Bean(name = "asyncPoolTaskExecutor")
public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(2);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setQueueCapacity(25);
taskExecutor.setKeepAliveSeconds(200);
taskExecutor.setThreadNamePrefix("thread-pool-");
// 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
@Override
public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
// 处理callable超时
configurer.setDefaultTimeout(60 * 1000);
configurer.setTaskExecutor(myThreadPoolTaskExecutor);
configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());
}
@Bean
public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {
return new TimeoutCallableProcessingInterceptor();
}
}
4.2 使用Future + 轮询(不推荐)
Future是JDK 1.5引入的接口,用于表示异步任务的结果。通过isDone()方法轮询任务是否完成,适用于简单的多任务结果聚合场景,但轮询会浪费CPU资源,不推荐在高并发场景使用。
实现示例
- 带 Future 返回的异步任务
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.scheduling.annotation.AsyncResult;
import java.util.concurrent.Future;
@Component
public class FutureAsyncTask {
/**
* 异步任务1:返回Future结果
*/
@Async
public Future<String> executeTaskOne() throws InterruptedException {
Thread.sleep(1000);
System.out.printf("TaskOne completed. Thread: %d%n", Thread.currentThread().getId());
return new AsyncResult<>("TaskOne Result");
}
/**
* 异步任务2:返回Future结果
*/
@Async
public Future<String> executeTaskTwo() throws InterruptedException {
Thread.sleep(1000);
System.out.printf("TaskTwo completed. Thread: %d%n", Thread.currentThread().getId());
return new AsyncResult<>("TaskTwo Result");
}
/**
* 异步任务3:返回Future结果
*/
@Async
public Future<String> executeTaskThree() throws InterruptedException {
Thread.sleep(1000);
System.out.printf("TaskThree completed. Thread: %d%n", Thread.currentThread().getId());
return new AsyncResult<>("TaskThree Result");
}
}
- Future 结果聚合接口
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.ResponseEntity;
import lombok.RequiredArgsConstructor;
import java.util.concurrent.Future;
@RestController
@RequiredArgsConstructor
public class FutureAsyncController {
private final FutureAsyncTask futureAsyncTask;
/**
* 测试Future结果聚合
*/
@RequestMapping("/testFutureAsync")
public ResponseEntity<String> testFutureAsync() throws Exception {
long startTime = System.currentTimeMillis();
// 提交三个异步任务
Future<String> taskOneResult = futureAsyncTask.executeTaskOne();
Future<String> taskTwoResult = futureAsyncTask.executeTaskTwo();
Future<String> taskThreeResult = futureAsyncTask.executeTaskThree();
// 轮询等待所有任务完成(不推荐,浪费CPU资源)
while (!(taskOneResult.isDone() && taskTwoResult.isDone() && taskThreeResult.isDone())) {
// 空轮询,可添加短暂睡眠减少CPU占用
Thread.sleep(10);
}
// 获取任务结果
String result = String.format("""
TaskOne Result: %s
TaskTwo Result: %s
TaskThree Result: %s
Total Time: %dms""",
taskOneResult.get(),
taskTwoResult.get(),
taskThreeResult.get(),
System.currentTimeMillis() - startTime);
return ResponseEntity.ok(result);
}
}
执行效果
总耗时约 1000ms(并行执行),结果如下:
TaskOne completed. Thread: 54
TaskTwo completed. Thread: 55
TaskThree completed. Thread: 56
TaskOne Result: TaskOne Result
TaskTwo Result: TaskTwo Result
TaskThree Result: TaskThree Result
Total Time: 1020ms
4.3 Spring的ListenableFuture和CountDownLatch处理
ListenableFuture是Spring提供的增强型Future,支持添加回调函数(SuccessCallback和FailureCallback),结合CountDownLatch可实现高效的多任务结果聚合,避免轮询带来的CPU浪费。
核心组件说明
- ListenableFuture:继承自
Future,支持添加回调函数 - CountDownLatch:同步工具类,用于等待多个线程完成操作(初始化计数器,线程完成后调用
countDown(),主线程调用await()等待) - SuccessCallback:任务成功完成时的回调接口
- FailureCallback:任务异常时的回调接口
- ListenableFuture 异步服务
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class ListenableFutureSearchService {
/**
* 异步搜索:模拟 Elasticsearch 查询
*/
@Async
public ListenableFuture<String> asyncSearch(String keyword) {
log.info("Searching keyword: {} by thread: {}", keyword, Thread.currentThread().getName());
try {
// 模拟搜索耗时
Thread.sleep(2000);
return new AsyncResult<>("Search result for [" + keyword + "]");
} catch (InterruptedException e) {
log.error("Search interrupted for keyword: {}", keyword, e);
// 封装异常结果
return new AsyncResult<>(null);
}
}
}
- 结果聚合服务
import org.springframework.stereotype.Service;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@Service
@RequiredArgsConstructor
public class ListenableFutureAggregateService {
private final ListenableFutureSearchService searchService;
/**
* 批量搜索:聚合多个关键词的搜索结果
*/
public List<String> batchSearch(List<String> keywords) throws InterruptedException {
// 初始化计数器:关键词数量
CountDownLatch latch = new CountDownLatch(keywords.size());
// 线程安全的结果列表
List<String> searchResults = Collections.synchronizedList(new ArrayList<>());
for (String keyword : keywords) {
ListenableFuture<String> future = searchService.asyncSearch(keyword);
// 添加回调函数
future.addCallback(new ListenableFutureCallback<>() {
@Override
public void onSuccess(String result) {
if (result != null) {
searchResults.add(result);
}
// 计数器减1
latch.countDown();
}
@Override
public void onFailure(Throwable ex) {
// 异常处理
searchResults.add("Search failed for [" + keyword + "]: " + ex.getMessage());
latch.countDown();
}
});
}
// 等待所有任务完成,最多等待10秒
boolean allCompleted = latch.await(10, TimeUnit.SECONDS);
if (!allCompleted) {
searchResults.add("Some search tasks are timeout");
}
return searchResults;
}
}
测试接口
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.ResponseEntity;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.List;
@RestController
@RequiredArgsConstructor
public class ListenableFutureController {
private final ListenableFutureAggregateService aggregateService;
/**
* 测试ListenableFuture批量搜索
*/
@RequestMapping("/testListenableFuture")
public ResponseEntity<List<String>> testListenableFuture() throws InterruptedException {
List<String> keywords = Arrays.asList("Java", "Spring", "Async");
List<String> results = aggregateService.batchSearch(keywords);
return ResponseEntity.ok(results);
}
}
执行效果
总耗时约 2000ms,结果如下:
[
"Search result for [Java]",
"Search result for [Spring]",
"Search result for [Async]"
]
4.4 使用CompletableFuture(推荐)
CompletableFuture是JDK 8引入的增强型Future,实现了Future和CompletionStage接口,支持链式调用、结果组合、异常处理等高级特性,是目前异步编程的最佳实践。
实现示例
- CompletableFuture 异步服务
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CompletableFuture;
@Service
@Slf4j
public class GitHubLookupService {
private final RestTemplate restTemplate;
// 构造函数注入RestTemplate
public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
/**
* 异步查询GitHub用户信息
*/
@Async
public CompletableFuture<User> findGitHubUser(String username) throws InterruptedException {
log.info("Looking up GitHub user: {} by thread: {}", username, Thread.currentThread().getName());
// 构建GitHub API URL
String url = String.format("https://api.github.com/users/%s", username);
// 同步调用GitHub API(实际项目建议使用异步HTTP客户端)
User user = restTemplate.getForObject(url, User.class);
// 模拟处理耗时
Thread.sleep(1000);
// 返回已完成的CompletableFuture
return CompletableFuture.completedFuture(user);
}
}
- User 实体类
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
/**
* GitHub用户实体类
* JsonIgnoreProperties:忽略未知的JSON字段
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
private String name; // 用户名
private String blog; // 博客地址
private String location; // 所在地
private int publicRepos; // 公开仓库数量
}
- 测试接口
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.ResponseEntity;
import lombok.RequiredArgsConstructor;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@RestController
@RequiredArgsConstructor
public class CompletableFutureController {
private final GitHubLookupService gitHubLookupService;
/**
* 测试CompletableFuture批量查询GitHub用户
*/
@RequestMapping("/testCompletableFuture")
public ResponseEntity<String> testCompletableFuture() throws Exception {
long startTime = System.currentTimeMillis();
// 1. 提交三个异步任务
CompletableFuture<User> user1 = gitHubLookupService.findGitHubUser("PivotalSoftware");
CompletableFuture<User> user2 = gitHubLookupService.findGitHubUser("CloudFoundry");
CompletableFuture<User> user3 = gitHubLookupService.findGitHubUser("Spring-Projects");
// 2. 等待所有任务完成(非阻塞)
CompletableFuture<Void> allFutures = CompletableFuture.allOf(user1, user2, user3);
// 3. 所有任务完成后,处理结果(链式调用)
String result = allFutures.thenApply(v -> {
try {
return "GitHub User Info:n" +
String.format("1. %s (Blog: %s, Repos: %d)%n",
user1.get().getName(), user1.get().getBlog(), user1.get().getPublicRepos()) +
String.format("2. %s (Blog: %s, Repos: %d)%n",
user2.get().getName(), user2.get().getBlog(), user2.get().getPublicRepos()) +
String.format("3. %s (Blog: %s, Repos: %d)%n",
user3.get().getName(), user3.get().getBlog(), user3.get().getPublicRepos()) +
String.format("Total Time: %dms", System.currentTimeMillis() - startTime);
} catch (Exception e) {
return "Error processing user info: " + e.getMessage();
}
}).get(); // 获取最终结果
return ResponseEntity.ok(result);
}
}
- 配置类(RestTemplate 和线程池)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class CompletableFutureConfig {
/**
* 配置RestTemplate
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
/**
* 配置CompletableFuture专用线程池
*/
@Bean(name = "completableFutureExecutor")
public Executor completableFutureExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("CompletableFuture-Thread-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
执行效果
总耗时约 1000ms,结果如下:
GitHub User Info:
1. Pivotal Software, Inc. (Blog: https://pivotal.io/, Repos: 156)
2. Cloud Foundry (Blog: https://www.cloudfoundry.org/, Repos: 228)
3. Spring (Blog: https://spring.io/, Repos: 112)
Total Time: 1050ms
五、基于WebAsyncTask实现
WebAsyncTask是Spring提供的Callable封装类,在Callable的基础上增加了超时处理、完成回调、异常处理等功能,适用于Web环境下的异步请求处理。
5.1 核心特性
- 超时回调:设置任务超时时间,超时后执行指定逻辑
- 完成回调:任务完成后(无论成功或失败)执行的逻辑
- 异常处理:可自定义异常处理策略
- 线程池指定:支持指定专用线程池
5.2 实现示例
- WebAsyncTask控制器
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.WebAsyncTask;
import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
public class WebAsyncTaskController {
/**
* WebAsyncTask示例:模拟订单处理
*/
@GetMapping("/processOrder")
public WebAsyncTask<String> processOrder() {
log.info("Request received by thread: {}", Thread.currentThread().getName());
// 创建WebAsyncTask,设置超时时间为60秒
WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(60 * 1000L, () -> {
log.info("Order processing started by thread: {}", Thread.currentThread().getName());
// 模拟订单处理耗时
Thread.sleep(3000);
// 模拟随机异常
if (Math.random() > 0.5) {
throw new RuntimeException("Order processing failed");
}
log.info("Order processing completed");
return "Order processed successfully";
});
// 超时回调:任务超时后执行
webAsyncTask.onTimeout(() -> {
log.error("Order processing timeout");
return "Order processing timeout, please try again later";
});
// 完成回调:任务完成后执行(无论成功或失败)
webAsyncTask.onCompletion(() -> {
log.info("Order processing task completed (success or failure)");
});
return webAsyncTask;
}
}
```
2. WebAsyncTask线程池配置
```
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebAsyncTaskConfig implements WebMvcConfigurer {
/**
* 配置WebAsyncTask专用线程池
*/
@Bean(name = "webAsyncTaskExecutor")
public ThreadPoolTaskExecutor webAsyncTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("WebAsyncTask-Thread-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
/**
* 配置WebAsyncTask支持
*/
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
// 设置WebAsyncTask超时时间
configurer.setDefaultTimeout(60 * 1000);
// 设置专用线程池
configurer.setTaskExecutor(webAsyncTaskExecutor());
}
}
```
## 5.3 执行效果
正常执行日志
```text
Request received by thread: http-nio-8080-exec-1
Order processing started by thread: WebAsyncTask-Thread-1
Order processing completed
Order processing task completed (success or failure)
```
超时执行日志
```text
Request received by thread: http-nio-8080-exec-2
Order processing started by thread: WebAsyncTask-Thread-2
Order processing timeout
Order processing task completed (success or failure)
```
异常执行日志
```text
Request received by thread: http-nio-8080-exec-3
Order processing started by thread: WebAsyncTask-Thread-3
Order processing task completed (success or failure)
[Exception] RuntimeException: Order processing failed
六、DeferredResult实现
DeferredResult是Spring提供的高级异步工具,与Callable的主要区别在于:DeferredResult的结果可以由其他线程(如MQ消费者、定时任务、外部系统回调)设置,支持跨线程通信,适用于实现长轮询、服务端推送、订单状态查询等场景。
6.1 核心特性
- 跨线程结果设置:结果可由非请求线程设置(如 MQ 消息触发)
- 超时处理:支持设置超时时间,超时后返回默认结果
- 完成回调:支持添加结果设置后的回调函数
- 取消处理:支持任务取消后的处理逻辑
6.2 典型应用场景
- 长轮询:客户端发起请求后,服务器 hold 住连接,有数据更新时再返回响应
- 订单状态查询:客户端发起订单状态查询,服务器等待订单状态变更后返回结果
- 服务端推送:模拟 WebSocket,实现简单的服务端数据推送
- 异步通知处理:等待外部系统回调(如支付回调)后返回结果
6.3 实现示例:长轮询订单状态查询
1. DeferredResult 控制器
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@RestController
@Slf4j
public class DeferredResultOrderController {
// 存储订单ID与DeferredResult的映射(线程安全)
private final Map<String, DeferredResult<String>> orderDeferredResultMap = new ConcurrentHashMap<>();
/**
* 长轮询查询订单状态
* @param orderId 订单ID
* @return DeferredResult
*/
@GetMapping("/queryOrderStatus/{orderId}")
public DeferredResult<String> queryOrderStatus(@PathVariable String orderId) {
log.info("Received order status query for orderId: {}, thread: {}",
orderId, Thread.currentThread().getName());
// 1. 创建DeferredResult,设置超时时间为30秒
DeferredResult<String> deferredResult = new DeferredResult<>(30 * 1000L,
"Order status query timeout, please try again later");
// 2. 存储订单ID与DeferredResult的映射
orderDeferredResultMap.put(orderId, deferredResult);
// 3. 完成回调:移除映射,释放资源
deferredResult.onCompletion(() -> {
orderDeferredResultMap.remove(orderId);
log.info("Order status query completed for orderId: {}, thread: {}",
orderId, Thread.currentThread().getName());
});
// 4. 超时回调:记录超时日志
deferredResult.onTimeout(() -> {
log.warn("Order status query timeout for orderId: {}", orderId);
});
// 5. 取消回调:处理客户端取消请求
deferredResult.onError((ex) -> {
log.error("Order status query error for orderId: {}", orderId, ex);
});
return deferredResult;
}
/**
* 模拟订单状态更新(如MQ消息触发、定时任务调用)
* @param orderId 订单ID
* @param status 新状态
*/
@GetMapping("/updateOrderStatus/{orderId}/{status}")
public String updateOrderStatus(@PathVariable String orderId, @PathVariable String status) {
log.info("Updating order status: orderId={}, status={}, thread: {}",
orderId, status, Thread.currentThread().getName());
// 1. 获取订单对应的DeferredResult
DeferredResult<String> deferredResult = orderDeferredResultMap.get(orderId);
if (deferredResult != null && !deferredResult.isSetOrExpired()) {
// 2. 设置订单状态结果(唤醒等待的请求)
String result = String.format("Order status updated: orderId=%s, status=%s", orderId, status);
deferredResult.setResult(result);
return "Order status updated successfully";
} else {
return "No pending query for this order or query has expired";
}
}
}
2. DeferredResult 配置
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.context.annotation.Bean;
@Configuration
public class DeferredResultConfig implements WebMvcConfigurer {
/**
* 配置DeferredResult专用线程池
*/
@Bean(name = "deferredResultExecutor")
public ThreadPoolTaskExecutor deferredResultExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("DeferredResult-Thread-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
/**
* 配置DeferredResult支持
*/
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
// 设置默认超时时间
configurer.setDefaultTimeout(30 * 1000);
// 设置专用线程池
configurer.setTaskExecutor(deferredResultExecutor());
}
}
6.4 执行流程与效果
1. 客户端发起订单状态查询
GET /queryOrderStatus/ORDER123
服务器日志:
Received order status query for orderId: ORDER123, thread: http-nio-8080-exec-1
此时客户端连接被 hold 住,等待订单状态更新。
2. 触发订单状态更新
GET /updateOrderStatus/ORDER123/PAID
服务器日志:
Updating order status: orderId=ORDER123, status=PAID, thread: http-nio-8080-exec-2
Order status query completed for orderId: ORDER123, thread: DeferredResult-Thread-1
3. 客户端接收响应
客户端收到订单状态更新结果:
Order status updated: orderId=ORDER123, status=PAID
4. 超时场景
若 30 秒内未触发订单状态更新,客户端收到超时响应:
Order status query timeout, please try again later
服务器日志:
Order status query timeout for orderId: ORDER123
Order status query completed for orderId: ORDER123, thread: DeferredResult-Thread-2
七、拓展
7.1 @Async、WebAsyncTask、Callable、DeferredResult的区别
所在的包不同:
- @Async:org.springframework.scheduling.annotation;
- WebAsyncTask:org.springframework.web.context.request.async;
- Callable:java.util.concurrent;
- DeferredResult:org.springframework.web.context.request.async;
通过所在的包,我们应该隐隐约约感到一些区别,比如@Async是位于scheduling包中,而WebAsyncTask和DeferredResult是用于Web(Spring MVC)的,而Callable是用于concurrent(并发)处理的。
对于Callable,通常用于Controller方法的异步请求,当然也可以用于替换Runnable的方式。在方法的返回上与正常的方法有所区别:
// 普通方法
public String aMethod(){
}
// 对照Callable方法
public Callable<String> aMethod(){
}
而WebAsyncTask是对Callable的封装,提供了一些事件回调的处理,本质上区别不大。
DeferredResult使用方式与Callable类似,重点在于跨线程之间的通信。@Async也是替换Runnable的一种方式,可以代替我们自己创建线程。而且适用的范围更广,并不局限于Controller层,而可以是任何层的方法上。
当然,大家也可以从返回结果,异常处理等角度来分析一下,这里就不再展开了。

浙公网安备 33010602011771号