文章目录
- 示例代码
- 代码执行结果
- 代码执行过程解析
- DeferredResult 的优势
本章内容主要讲讲基于DeferredResult的异步处理
在 Servlet 容器中启用了异步请求处理功能,控制器方法就可以用 包装任何支持的控制器方法返回值DeferredResult,控制器可以从不同的线程异步产生返回值 — — 例如,响应外部事件(JMS 消息)、计划任务或其他事件
简单的讲述整个异步逻辑,就是一个请求过来,以往tomcat会从自身容器取出一个线程去执行逻辑,这个容器线程在逻辑没处理完之前,一直处于阻塞状态,其他请求进来也无法使用。使用本章介绍的DeferredResult后,若业务中有自定义线程池去处理逻辑,那tomcat的容器线程则可以释放,逻辑处理完回来会根据保存的上下文将结果写回到请求中去
同步阻塞:
异步非阻塞:
示例代码
@Configuration
@EnableAsync
public class AsyncConfig {
private int corePoolSize = 16;
private int maxPoolSize = 90;
private int queueCapacity = 2000;
//指定当前线程池为主线程池
@Bean("taskExecutor")
@Primary
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(corePoolSize);
taskExecutor.setMaxPoolSize(maxPoolSize);
taskExecutor.setQueueCapacity(queueCapacity);
taskExecutor.setKeepAliveSeconds(60);
// 拒绝策略-抛异常
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
taskExecutor.setThreadGroupName("Task-");
taskExecutor.setThreadNamePrefix("common-");
taskExecutor.setBeanName("taskExecutor");
return taskExecutor;
}
}
@RestController
@RequestMapping("/mytest")
public class AsyncController {
@Autowired
private TaskExecutor taskExecutor;
@GetMapping("/async")
public DeferredResult<Integer> async() {
System.out.println("当前线程 外部 " + Thread.currentThread().getName());
DeferredResult<Integer> result = new DeferredResult<>();
CompletableFuture.supplyAsync(this::count,taskExecutor)
.whenCompleteAsync((res, throwable) -> {
if (throwable != null) {
result.setErrorResult(throwable.getMessage());
}else{
result.setResult(res);
}
});
return result;
}
private Integer count() {
System.out.println("内部线程 名称 "+Thread.currentThread().getName());
System.out.println("-------start---------------");
int count = 0;
for (int i = 0; i < 1000000; i++) {
for (int j = 0; j < 10000; j++) {
count = count + j;
}
}
System.out.println("-------end---------------");
return count;
}
}
代码执行结果
代码执行过程解析
上述代码我们创建了一个业务线程池taskExecutor,然后controller方法在async DeferredResult内创建了一个DeferredResult对象,接着使用CompletableFuture向业务线程池taskExecutor提交我们的请求处理逻辑(其内部处理完毕后把结果设置到创建的DeferredResult),最后返回创建的DeferredResult对象。其整个处理过程如下:
1)Tomcat容器接收路径为personDeferredResult的请求后,会分配一个容器线程来执行DispatcherServlet进行请求分派,请求被分到含有/async路径的controller,然后执行async方法,该方法内创建了一个DeferredResult对象,接着把处理任务提交到了线程池进行处理,最后返回DeferredResult对象。
2)Spring MVC内部在/async方法返回后会保存DeferredResult对象到内存队列或者列表,然后会调用request.startAsync()开启异步处理,并且调用DeferredResult对象的setResultHandler方法,设置当异步结果产生后对结果进行重新路由的回调函数(逻辑在WebAsyncManager的startDeferredResultProcessing方法),接着释放分配给当前请求的容器线程,与此同时当前请求的DispatcherServlet和所有filters也执行完毕了,但是response流还是保持打开(因为任务执行结果还没写回)。
3)最终在业务线程池中执行的异步任务会产生一个结果,该结果会被设置到DeferredResult对象,然后设置的回调函数会被调用,接着Spring MVC会分派请求结果回到Servlet容器继续完成处理,DispatcherServlet被再次调用,使用返回的异步结果继续进行处理,最终把响应结果写回请求方。
DeferredResult 的优势
使用 DeferredResult 实现异步请求处理肯定有一定的优势和好处,这里总结三点
-
资源利用率高
通过将耗时操作放到异步线程中执行,可以避免主线程阻塞,提高系统的并发处理能力和资源利用率。 -
灵活性高
DeferredResult 允许手动设置异步处理结果,可以在多个线程中执行操作,适用于复杂的异步处理场景。 -
超时和异常处理
DeferredResult 提供了超时处理和异常处理机制,允许开发者定义超时和异常处理逻辑,提高了异步请求处理的可靠性和健壮性。