使用同步请求模型,所有的动作都交给同一个Tomcat线程处理。处理完所有动作后,线程将被释放回线程池。试想一下,如果业务需要处理很长时间,那么这个Tomcat线程其实一直都在被占用。随着请求越来越多,可用的I/O线程越来越少,直到耗尽。这时候后续的请求只能等待一个空闲的Tomcat线程,这样会增加请求的执行时间。如果客户端不关心返回业务结果,那么我们可以自定义线程池,将请求任务提交到线程池,然后立即返回。您还可以使用Spring异步任务。有兴趣的可以自行搜索资料。但是在很多场景下,客户端需要对返回的结果进行处理,我们不能使用上面的方案。在Servlet2时代,我们没有办法对上面的方案进行优化。但是等到Servlet3,异步Servlet新特性的引入,可以完美的解决上面的需求。异步Servlet执行请求流程:将请求信息解析成HttpServletRequest分发给具体的Servlet处理,提交业务到自定义业务线程池,请求立即返回,Tomcat线程立即释放。当业务线程完成任务执行后,将结果转发Tomcat线程通过HttpServletResponse将响应结果返回给等待的客户端。引入异步Servlet3的整体流程如下:使用异步Servlet,Tomcat线程只处理请求解析动作,所有耗时的业务操作都交给业务线程池处理,所以相比同步请求,Tomcat线程可以处理更多请求。虽然我们将业务处理交给了业务线程池进行异步处理,但是对于客户端来说,还是在同步等待响应结果。可能有同学认为异步请求会得到更快的响应时间,其实不然。相反,可能会因为引入更多的线程而增加线程上下文切换的时间。虽然响应时间没有减少,但是异步请求带来了其他明显的优势:它可以处理更多的并发连接,提高系统的整体吞吐量。请求解析与业务处理完全分离,职责单一。自定义业务线程池,我们可以更方便的对其进行监控、降级等处理,可以根据不同的业务进行自定义。不同的线程池相互隔离,不需要相互影响。因此,在具体的使用过程中,我们还需要进行相应的压力测试,观察响应时间和吞吐量等指标,综合选择。如何使用异步servlet使用异步servlet并不难。阿粉总结就是以下三招:HttpServletRequest#startAsync获取AsyncContext异步上下文对象使用自定义业务线程池处理业务逻辑业务线程处理完成,通过AsyncContext#complete返回响应结果下面的例子将使用SpringBoot,Web容器选择Tomcat。示例代码如下:ExecutorServiceexecutorService=Executors.newFixedThreadPool(10);@RequestMapping("/hello")publicvoidhello(HttpServletRequestrequest){AsyncContextasyncContext=request.startAsync();//超时asyncContext.setTimeout(10000);executorService。submit(()->{try{//休眠5s,模拟业务运行TimeUnit.SECONDS.sleep(5);//输出响应结果asyncContext.getResponse().getWriter().println("helloworld");log.info("异步线程处理结束");}catch(Exceptione){e.printStackTrace();}finally{asyncContext.complete();}});log.info("servlet线程处理结束");}访问该请求的浏览器会同步等待5s得到输出响应,应用日志输出结果如下:2020-03-2407:27:08.997INFO79257---[nio-8087-exec-4]com。xxxx:servletthreadprocessingended2020-03-2407:27:13.998INFO79257---[pool-1-thread-3]com.xxxx:Asynchronousthreadprocessingended这里需要注意设置一个reaso可设置超时时间,防止客户端长时间等待。SpringMVCServlet3API无法使用SpringMVC提供的特性。我们需要自己处理响应信息,比较繁琐。SpringMVC3.2引入了基于Servlet3的异步请求处理方式。我们可以像使用同步请求一样轻松地使用异步请求。SpringMVC提供了两种异步方法,只需将Controller方法的返回值修改为如下类:DeferredResultCallableDeferredResultDeferredResult是SpringMVC3.2之后引入的新类。只要请求方法返回DeferredResult,就可以快速使用异步请求。示例代码如下:ExecutorServiceexecutorService=Executors.newFixedThreadPool(10);@RequestMapping("/hello_v1")publicDeferredResulthello_v1(){//设置超时DeferredResultdeferredResult=newDeferredResult<>(7000L);//异步线程处理结束,回调方法deferredResult.onCompletion(()->{log.info("异步线程处理结束");});//如果异步线程执行时间超过设置的超时时间,则回调方法deferredResult将被执行。onTimeout(()->{log.info("异步线程超时");//设置返回结果deferredResult.setErrorResult("timeouterror");});deferredResult.onError(throwable->{log.error("Exception",throwable);//设置返回结果deferredResult.setErrorResult("othererror");});executorService.submit(()->{try{TimeUnit.SECONDS.sleep(5);deferredResult.setResult("hello_v1");//设置返回结果}catch(Exceptione){e.printStackTrace();//如果异步方法内部异常deferredResult.setErrorResult("error");}});log.info("servlet线程处理结束");returndeferredResult;}创建DeferredResult实例时,可以传入具体的超时时间。另外,我们可以设置默认超时时间:#异步请求超时时间spring.mvc.async.request-timeout=2000如果执行异步程序,可以调用DeferredResult#setResult返回响应结果。此时如果设置了DeferredResult#onCompletion回调方法,就会触发该回调方法。同时,我们还可以设置超时回调方法DeferredResult#onTimeout,一旦异步线程执行超时,就会触发该回调方法。最后,DeferredResult还提供了其他异常回调方法onError。一开始阿芬以为只要异步线程出现异常就会触发这个回调方法。尝试在异步线程内抛出异常,但无法成功触发。后续阿芬查看了该方法的文档。当web容器线程处理异步请求时出现异常,可以成功触发。CallableSpring也提供了一种使用异步请求的方式,直接使用JDKCallable。示例代码如下:@RequestMapping("/hello_v2")publicCallablehello_v2(){returnnewCallable(){@OverridepublicStringcall()throwsException{TimeUnit.SECONDS.sleep(5);log.info("异步方法结束");return"hello_v2";}};}默认直接执行会输出WARN日志。这是因为SimpleAsyncTaskExecutor默认是用来执行异步请求的,每次调用执行都会创建一个新的线程。由于这种方式没有复用线程,所以不推荐在生产中采用这种方式,所以我们需要使用线程池来代替。我们可以使用如下方式自定义线程池:@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)publicAsyncTaskExecutorexecutor(){ThreadPoolTask??ExecutorthreadPoolTask??Executor=newThreadPoolTask??Executor();threadPoolTask??Executor.setThreadNamePrefix("test-");threadPoolTask??Executor.setCorePoolSize(10);threadPoolTask??Executor.setMaxPoolSize(20);returnthreadPoolTask??Executor;}注意Bean名称必须是applicationTaskExecutor,否则Spring不会使用自定义的线程池。或者可以直接使用SpringBoot配置文件配置代替:#核心线程数spring.task.execution.pool.core-size=10#最大线程数spring.task.execution.pool.max-size=20#线程名prefixspring.task.execution.thread-name-prefix=test#还有其他的配置,读者可以配置异步请求的超时时间,这种方式只能通过配置文件来配置。spring.mvc.async.request-timeout=10000如果我们需要为单个请求配置特定的超时,我们需要用WebAsyncTask包装Callable。@RequestMapping("/hello_v3")publicWebAsyncTaskhello_v3(){System.out.println("asdas");Callablecallable=newCallable(){@OverridepublicStringcall()throwsException{TimeUnit.SECONDS。sleep(5);log.info("异步方法结束");return"hello_v3";}};//unitmsWebAsyncTaskwebAsyncTask=newWebAsyncTask<>(10000,callable);returnwebAsyncTask;}SpringMVC总结两种异步请求方法本质上是帮我们包装Servlet3API,让我们不用关心具体的实现细节。虽然我们在日常使用中一般会选择使用SpringMVC两种异步请求方式,但是我们还是需要了解一下异步请求的实际原理。所以在使用之前,可以先尝试使用Servlet3API进行练习,后面再使用SpringMVC。参考https://www.baeldung.com/spring-deferred-resulthttps://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-支持