007-优化web请求三-异步调用【WebAsyncTask】

摘要:
现在考虑一下,如果在处理过程中需要调用后端的业务逻辑服务器请求处理线程,它将在调用后等待Return。异步和异步的最大区别是,当请求处理线程调用后台处理时,“它将连接一个回调处理线程来处理调用结果,回调处理线程将内容返回给浏览器。请求处理线程不需要阻塞,并通过SpringMVC管理的线程生成返回值,该返回值将再次分配给servlet容器以恢复处理过程。

一、什么是同步调用

  007-优化web请求三-异步调用【WebAsyncTask】第1张

  浏览器发起请求,Web服务器开一个线程处理,处理完把处理结果返回浏览器。好像没什么好说的了,绝大多数Web服务器都如此般处理。现在想想如果处理的过程中需要调用后端的一个业务逻辑服务器

  007-优化web请求三-异步调用【WebAsyncTask】第2张

  请求处理线程会在Call了之后等待Return,自身处于阻塞状态。这也是绝大多数Web服务器的做法。一般此种做法主要适用于,后端处理响应比较快,并且并发数比较低的情况。

  主要弊端,在高并发请求下,请求处理线程的短缺!因为请求处理线程的总数是有限的,如果类似的请求多了,所有的处理线程处于阻塞的状态,那新的请求也就无法处理了,也就所谓影响了服务器的吞吐能力。要更加好地发挥服务器的全部性能,就要使用异步。

二、什么是异步

  007-优化web请求三-异步调用【WebAsyncTask】第3张

  最大的不同在于请求处理线程对后台处理的调用使用了“invoke”的方式,就是说调了之后直接返回,而不等待,这样请求处理线程就“自由”了,它可以接着去处理别的请求,当后端处理完成后,会钩起一个回调处理线程来处理调用的结果,这个回调处理线程跟请求处理线程也许都是线程池中的某个线程,相互间可以完全没有关系,由这个回调处理线程向浏览器返回内容。这就是异步的过程。

  带来的改进是显而易见的,请求处理线程不需要阻塞了,它的能力得到了更充分的使用,带来了服务器吞吐能力的提升。

三、使用Spring MVC 和Servlet3异步线程

3.1、前提

要使用Spring MVC的异步功能,你得先确保你用的是Servlet 3.0或以上的版本,Maven中如此配置:

<dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>

Spring MVC 3.2以后版本开始引入了基于Servlet3的异步请求处理

3.2、概述

  相比以前,控制器方法已经不一定需要一个值,而是可以直接返回一个Callable对象,并通过Spring MVC所管理的线程来产生返回值,与此同时,Servlet容器的主线程则可以退出并释放其资源,同时也允许容器去处理其它请求。通过一个TaskExecutor,Spring MVC可以在另外的线程中调用Callable。当Callable返回时,请求在携带Callable返回的值,再次被分配到Servlet容器中恢复处理流程。

  官方文档中说DeferredResult和Callable都是为了异步生成返回值提供基本的支持。简单来说就是一个请求进来,如果你使用了DeferredResult或者Callable,在没有得到返回数据之前,DispatcherServlet和所有Filter就会退出Servlet容器线程,但响应保持打开状态,一旦返回数据有了,这个DispatcherServlet就会被再次调用并且处理,以异步产生的方式,向请求端返回值。 

  这么做的好处就是请求不会长时间占用服务连接池,提高服务器的吞吐量。

1、Servlet 3.0异步请求运作机制的部分原理

  a.Servlet请求ServletRequest可以通过调用request.startAsync()方法而进入异步模式,这样做的主要结果就是该Servlet以及所有的过滤器都可以结束但其相应(Response)会留待异步处理结束后在返回调用。

  b.request.startAsync()方法会返回一个AsyncContext对象,可以用他对异步处理进行进一步的控制和操作,比如说他也提供了一个与反转(forward)很相似的dispatch方法,只不过他允许应用恢复Servlet容器的请求处理进程。

  c.ServletRequest提供了获取当前DispatherType的方式,后者可以用来区别当前处理的是原始请求,异步分发请求,转向或者是其它类型的请求分发类型。

2、Callable的异步请求被处理时所发生的事件

官方介绍

Controller returns a Callable.
Spring MVC calls request.startAsync() and submits the Callable to a TaskExecutor for processing in a separate thread.
Meanwhile the DispatcherServlet and all Filter’s exit the Servlet container thread but the response remains open.
Eventually the Callable produces a result and Spring MVC dispatches the request back to the Servlet container to complete processing.
The DispatcherServlet is invoked again and processing resumes with the asynchronously produced return value from the Callable.

  1》Controller返回Callable
  2》Spring MVC调用request.startAsync()并将Callable提交给TaskExecutor,以便在单独的线程中进行处理。
  3》同时DispatcherServlet和所有Filter都退出Servlet容器线程,但响应仍保持打开状态。
  4》最终,Callable生成一个结果,Spring MVC将请求调度回Servlet容器以完成处理。
  5》再次调用DispatcherServlet,并使用来自Callable的异步生成的返回值继续处理。

  流程上大体与DeferredResult类似,只不过Callable是由TaskExecutor来处理的,而TaskExecutor继承自java.util.concurrent.Executor。我们来看一下它的源代码,它也是在WebAysncManager中处理的:

007-优化web请求三-异步调用【WebAsyncTask】第4张007-优化web请求三-异步调用【WebAsyncTask】第5张
/**
     * Use the given {@link WebAsyncTask} to configure the task executor as well as
     * the timeout value of the {@code AsyncWebRequest} before delegating to
     * {@link #startCallableProcessing(Callable, Object...)}.
     * @param webAsyncTask a WebAsyncTask containing the target {@code Callable}
     * @param processingContext additional context to save that can be accessed
     * via {@link #getConcurrentResultContext()}
     * @throws Exception if concurrent processing failed to start
     */
    public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object... processingContext) throws Exception {
        Assert.notNull(webAsyncTask, "WebAsyncTask must not be null");
        Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");

        Long timeout = webAsyncTask.getTimeout();
        if (timeout != null) {
            this.asyncWebRequest.setTimeout(timeout);
        }

        AsyncTaskExecutor executor = webAsyncTask.getExecutor();
        if (executor != null) {
            this.taskExecutor = executor;
        }

        List<CallableProcessingInterceptor> interceptors = new ArrayList<CallableProcessingInterceptor>();
        interceptors.add(webAsyncTask.getInterceptor());
        interceptors.addAll(this.callableInterceptors.values());
        interceptors.add(timeoutCallableInterceptor);

        final Callable<?> callable = webAsyncTask.getCallable();
        final CallableInterceptorChain interceptorChain = new CallableInterceptorChain(interceptors);

        this.asyncWebRequest.addTimeoutHandler(new Runnable() {
            @Override
            public void run() {
                logger.debug("Processing timeout");
                Object result = interceptorChain.triggerAfterTimeout(asyncWebRequest, callable);
                if (result != CallableProcessingInterceptor.RESULT_NONE) {
                    setConcurrentResultAndDispatch(result);
                }
            }
        });

        this.asyncWebRequest.addCompletionHandler(new Runnable() {
            @Override
            public void run() {
                interceptorChain.triggerAfterCompletion(asyncWebRequest, callable);
            }
        });

        interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, callable);
        startAsyncProcessing(processingContext);
        //启动线程池的异步处理
        try {
            this.taskExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    Object result = null;
                    try {
                        interceptorChain.applyPreProcess(asyncWebRequest, callable);
                        result = callable.call();
                    }
                    catch (Throwable ex) {
                        result = ex;
                    }
                    finally {
                        result = interceptorChain.applyPostProcess(asyncWebRequest, callable, result);
                    }
                    //设置当前的结果并转发
                    setConcurrentResultAndDispatch(result);
                }
            });
        }
        catch (RejectedExecutionException ex) {
            Object result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, ex);
            setConcurrentResultAndDispatch(result);
            throw ex;
        }
    }
View Code

  对比DeferredResult,在这里刚开始也是添加拦截器,只不过拦截器的名称是CallableProcessingInterceptor ,同时也需要设置WebAsyncRequest的超时处理,完成时处理的响应操作。这其中最大的区别就是使用TaskExecutor来对Callable进行异步处理

3、DeferredResult对象请求的处理顺序也非常类似,区别在于应用可以通过任何线程来计算返回一个结果

官网描述

DeferredResult processing:

Controller returns a DeferredResult and saves it in some in-memory queue or list where it can be accessed.
Spring MVC calls request.startAsync().
Meanwhile the DispatcherServlet and all configured Filter’s exit the request processing thread but the response remains open.
The application sets the DeferredResult from some thread and Spring MVC dispatches the request back to the Servlet container.
The DispatcherServlet is invoked again and processing resumes with the asynchronously produced return value.

  1》将Controller返回的DeferredResult值保存到内存队列或集合当中以便存取

  2》SpringMVC调用HttpServletRequeststartAsync()方法,异步处理

  3》同时,DispatcherServlet和所有已配置的Filter都退出请求处理线程,但响应仍保持打开状态,此时方法的响应对象仍未返回。

  4》应用程序从某个线程设置DeferredResult,Spring MVC将请求调度回Servlet容器,恢复处理

  5》再次调用DispatcherServlet,并使用异步生成的返回值继续处理

源码分析:

  当一个请求被DispatcherServlet处理时,会试着获取一个WebAsyncManager对象

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

          // 获取WebAsyncManager
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        try {
          // ......省略部分代码
          // 执行子控制器的方法
          mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        //如果当前的请求需要异步处理,则终止当前请求,但是响应是打开的
          if (asyncManager.isConcurrentHandlingStarted()) {
              return;
          }
        //....省略部分代码
       }
        //....省略部分代码
}

  对于每一个子控制器的方法返回值,都是HandlerMethodReturnValueHandler接口处理的,其中有一个实现类是DeferredResultMethodReturnValueHandler,关键代码如下:

007-优化web请求三-异步调用【WebAsyncTask】第4张007-优化web请求三-异步调用【WebAsyncTask】第7张
package org.springframework.web.servlet.mvc.method.annotation;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletionStage;
import java.util.function.BiFunction;

import org.springframework.core.MethodParameter;
import org.springframework.lang.UsesJava8;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;

/**
 * Handler for return values of type {@link DeferredResult}, {@link ListenableFuture},
 * {@link CompletionStage} and any other async type with a {@link #getAdapterMap()
 * registered adapter}.
 *
 * @author Rossen Stoyanchev
 * @since 3.2
 */
@SuppressWarnings("deprecation")
public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {

    //存放DeferredResult的适配集合
    private final Map<Class<?>, DeferredResultAdapter> adapterMap;


    public DeferredResultMethodReturnValueHandler() {
        this.adapterMap = new HashMap<Class<?>, DeferredResultAdapter>(5);
        this.adapterMap.put(DeferredResult.class, new SimpleDeferredResultAdapter());
        this.adapterMap.put(ListenableFuture.class, new ListenableFutureAdapter());
        if (ClassUtils.isPresent("java.util.concurrent.CompletionStage", getClass().getClassLoader())) {
            this.adapterMap.put(CompletionStage.class, new CompletionStageAdapter());
        }
    }


    /**
     * Return the map with {@code DeferredResult} adapters.
     * <p>By default the map contains adapters for {@code DeferredResult}, which
     * simply downcasts, {@link ListenableFuture}, and {@link CompletionStage}.
     * @return the map of adapters
     * @deprecated in 4.3.8, see comments on {@link DeferredResultAdapter}
     */
    @Deprecated
    public Map<Class<?>, DeferredResultAdapter> getAdapterMap() {
        return this.adapterMap;
    }

    private DeferredResultAdapter getAdapterFor(Class<?> type) {
        for (Class<?> adapteeType : getAdapterMap().keySet()) {
            if (adapteeType.isAssignableFrom(type)) {
                return getAdapterMap().get(adapteeType);
            }
        }
        return null;
    }


    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return (getAdapterFor(returnType.getParameterType()) != null);
    }

    @Override
    public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
        return (returnValue != null && (getAdapterFor(returnValue.getClass()) != null));
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

        if (returnValue == null) {
            mavContainer.setRequestHandled(true);
            return;
        }
       //根据返回值的类型获取对应的DeferredResult适配器
        DeferredResultAdapter adapter = getAdapterFor(returnValue.getClass());
        if (adapter == null) {
            throw new IllegalStateException(
                    "Could not find DeferredResultAdapter for return value type: " + returnValue.getClass());
        }
        DeferredResult<?> result = adapter.adaptToDeferredResult(returnValue);
        //开启异步请求
        WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer);
    }

}
View Code

  在这里我们重点关注handleReturnValue的方法,在经过适配包装后获取DeferredResult开启了异步之旅

  紧接着查看handleReturnValue方法中调用的WebAsyncManager的startDeferredResultProcessing方法

007-优化web请求三-异步调用【WebAsyncTask】第4张007-优化web请求三-异步调用【WebAsyncTask】第9张
    public void startDeferredResultProcessing(
            final DeferredResult<?> deferredResult, Object... processingContext) throws Exception {

        Assert.notNull(deferredResult, "DeferredResult must not be null");
        Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
        //设置超时时间
        Long timeout = deferredResult.getTimeoutValue();
        if (timeout != null) {
            this.asyncWebRequest.setTimeout(timeout);
        }

        //获取所有的延迟结果拦截器
        List<DeferredResultProcessingInterceptor> interceptors = new ArrayList<DeferredResultProcessingInterceptor>();
        interceptors.add(deferredResult.getInterceptor());
        interceptors.addAll(this.deferredResultInterceptors.values());
        interceptors.add(timeoutDeferredResultInterceptor);

        final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain(interceptors);
       
        this.asyncWebRequest.addTimeoutHandler(new Runnable() {
            @Override
            public void run() {
                try {
                    interceptorChain.triggerAfterTimeout(asyncWebRequest, deferredResult);
                }
                catch (Throwable ex) {
                    setConcurrentResultAndDispatch(ex);
                }
            }
        });

        this.asyncWebRequest.addCompletionHandler(new Runnable() {
            @Override
            public void run() {
                interceptorChain.triggerAfterCompletion(asyncWebRequest, deferredResult);
            }
        });

        interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, deferredResult);
         //开始异步处理
        startAsyncProcessing(processingContext);

        try {
            interceptorChain.applyPreProcess(this.asyncWebRequest, deferredResult);
            deferredResult.setResultHandler(new DeferredResultHandler() {
                @Override
                public void handleResult(Object result) {
                    result = interceptorChain.applyPostProcess(asyncWebRequest, deferredResult, result);
                    //设置结果并转发
                    setConcurrentResultAndDispatch(result);
                }
            });
        }
        catch (Throwable ex) {
            setConcurrentResultAndDispatch(ex);
        }
    }

    private void startAsyncProcessing(Object[] processingContext) {
        clearConcurrentResult();
        this.concurrentResultContext = processingContext;
        //实际上是执行的是HttpServletRequest对应方法
        this.asyncWebRequest.startAsync();

        if (logger.isDebugEnabled()) {
            HttpServletRequest request = this.asyncWebRequest.getNativeRequest(HttpServletRequest.class);
            String requestUri = urlPathHelper.getRequestUri(request);
            logger.debug("Concurrent handling starting for " + request.getMethod() + " [" + requestUri + "]");
        }
    }
View Code

   在这里首先收集所有配置好的DeferredResultProcessingInterceptor ,然后设置asyncRequest的超时处理,完成时的处理等,同时会分阶段执行拦截器中的各个方法。最后我们关注一下如下代码:

            deferredResult.setResultHandler(result -> {
                result = interceptorChain.applyPostProcess(this.asyncWebRequest, deferredResult, result);
                //设置结果并转发
                setConcurrentResultAndDispatch(result);
            });

  查看setConcurrentResultAndDispatch内实现:其最终还是要调用AsyncWebRequest接口中的dispatch方法进行转发,让DispatcherServlet重新处理异步结果:this.asyncWebRequest.dispatch();

  其实在这里都是封装自HttpServletRequest的异步操作,我们可以看一下StandardServletAsyncWebRequest的类结构图:

    007-优化web请求三-异步调用【WebAsyncTask】第10张

  可以在其父类ServletRequestAttributes里找到对应的实现:

    private final HttpServletRequest request;
/**
     * Exposes the native {@link HttpServletRequest} that we're wrapping.
     */
    public final HttpServletRequest getRequest() {
        return this.request;
    }

  StandardServletAsyncWebRequest代码,方便理解整个异步是怎么执行:

   //java.servlet.AsnycContext
    private AsyncContext asyncContext;
  
    @Override
    public void startAsync() {
        Assert.state(getRequest().isAsyncSupported(),
                "Async support must be enabled on a servlet and for all filters involved " +
                "in async request processing. This is done in Java code using the Servlet API " +
                "or by adding "<async-supported>true</async-supported>" to servlet and " +
                "filter declarations in web.xml.");
        Assert.state(!isAsyncComplete(), "Async processing has already completed");

        if (isAsyncStarted()) {
            return;
        }
        this.asyncContext = getRequest().startAsync(getRequest(), getResponse());
        this.asyncContext.addListener(this);
        if (this.timeout != null) {
            this.asyncContext.setTimeout(this.timeout);
        }
    }

    @Override
    public void dispatch() {
        Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext");
        this.asyncContext.dispatch();
    } 

4、异步结果的异常处理:

  如果Callable在执行过程中抛出异常 与一般的控制器异常一样,会被正常的异常处理流程捕获处理

  如果返回方法是一个DeferredResult对象,可以选择

deferredResult.setErrorResult()

5、拦截异步请求

  处理连接器HandlerInterceptor可以实现AsyncHandlerInterceptor接口拦截异步请求,因为在异步请求的开始时,被调用的回调方法是该接口的afterConcurrentHandlingStarted方法,而不是一般的postHandle 和 afterCompletion方法。如果需要与异步请求处理的生命流程有更深入的集成,比如需要处理timeout的事件等,则HandlerInterceptor需要注册CallableProcessingInterceptor或DeferredResultProcessingInterceptor拦截器,更多细节需要参考AsyncHandlerInterceptor类的Java文档。

  DeferredResult类还提供了onTimeout(Runnable)和onCompletion(Runnable)等方法可以参考DeferredResult的java文档

  Callable需要请求过期(timeout)和完成后的拦截时,可以把他包装在一个WebAsyncTask实例中,后者提供了相关技术支持。

3.3、异步拦截器

  1)、原生API的AsyncListener
  2)、SpringMVC:实现AsyncHandlerInterceptor;

四、使用

4.1、Callable使用

@RestController
public class WebCallableAsyncController {
    Logger log=LoggerFactory.getLogger(WebCallableAsyncController.class);
    @GetMapping("/callable")
    public Callable<String> testCallable() throws Exception {
        log.info("主线程开始!");

        Callable<String> result = new Callable<String>() {
            @Override
            public String call() throws Exception {
                log.info("副线程开始1!");
                Thread.sleep(3000);
                log.info("副线程结束1!");
                return "SUCCESS1";
            }
        };        
        log.info("主线程结束!");
        return result;
    }
}

请求地址查看

2019-02-27 14:25:00.197  INFO 13815 --- [nio-8080-exec-3] c.g.b.g.d.w.WebCallableAsyncController   : 主线程开始!
2019-02-27 14:25:00.197  INFO 13815 --- [nio-8080-exec-3] c.g.b.g.d.w.WebCallableAsyncController   : 主线程结束!
2019-02-27 14:25:00.197  INFO 13815 --- [      MvcAsync2] c.g.b.g.d.w.WebCallableAsyncController   : 副线程开始1!
2019-02-27 14:25:03.200  INFO 13815 --- [      MvcAsync2] c.g.b.g.d.w.WebCallableAsyncController   : 副线程结束1!

  返回Callable意味着Spring MVC将调用在不同的线程中执行定义的任务。Spring将使用TaskExecutor来管理线程。在等待完成的长期任务之前,servlet线程将被释放。

  在长时间运行的任务执行完毕之前就已经从servlet返回了。这并不意味着客户端收到了一个响应。与客户端的通信仍然是开放的等待结果,但接收到的请求的线程已被释放,并可以服务于另一个客户的请求。

 4.2、DeferredResult使用

  一旦在Servlet容器中启用了异步请求处理功能,控制器方法就可以使用DeferredResult包装任何支持的控制器方法返回值,

     DeferredResult这个类代表延迟结果,我们先看一看spring的API文档给我们的解释:

{@code DeferredResult} provides an alternative to using a {@link Callable} for asynchronous request processing. 
While a {@code Callable} is executed concurrently on behalf of the application,
with a {@code DeferredResult} the application can produce the result from a thread of its choice.

  根据文档说明DeferredResult可以替代Callable来进行异步的请求处理。只不过这个类可以从其他线程里拿到对应的结果。当使用DeferredResult,我们可以将DefferedResult的类型并将其保存到可以获取到该对象的地方,比如说队列或者集合当中,这样方便其它线程能够取到并设置DefferedResult的值。

@RestController
public class WebDeferredResultAsyncController {
    Logger log = LoggerFactory.getLogger(WebDeferredResultAsyncController.class);
    //接收队列
    private BlockingQueue<DeferredResult<String>> blockingQueue = new ArrayBlockingQueue(1024);
    //接收队列 或者ConcurrentLinkedQueue
    private static Queue<DeferredResult<String>> queue = new ConcurrentLinkedQueue<DeferredResult<String>>();
    
    /**
     * 返回值是DeferredResult类型,如果没有结果请求阻塞
     *
     * @return
     */
    @GetMapping("/quotes")
    public DeferredResult<String> quotes() {
        //指定超时时间,及出错时返回的值
        DeferredResult<String> result = new DeferredResult(3000L, "error");
        blockingQueue.add(result);
//        queue.add(result);
        return result;
    }

    /**
     * 另外一个请求(新的线程)设置值
     *
     * @throws InterruptedException
     */

    @GetMapping("take")
    public void take() throws InterruptedException {
        DeferredResult<String> result = blockingQueue.take();
        result.setResult("route");
//        DeferredResult<String> poll = queue.poll();
//        poll.setResult("OK");
    }
}

  控制器可以从不同的线程异步生成返回值,例如响应外部事件(JMS消息)、计划任务等,那么在这里我先使用另外一个请求来模拟这个过程
  此时我们启动tomcat,先访问地址http://localhost:8080/quotes ,此时我们会看到发送的请求由于等待响应遭到了阻塞:

  当在规定时间内访问http://localhost:8080/take 时,则能成功显示结果:

  如果有另一个线程给DeferredResult赋值后,DeferredResult在感知到自己的对象被赋值后就返回页面成功;

一个独立的示例

    /**
     * 一个独立的示例
     * @return
     */
    @RequestMapping(value = "/deferred", method = RequestMethod.GET)
    public DeferredResult<String> executeSlowTask() {
        log.info("Request received");
        DeferredResult<String> deferredResult = new DeferredResult<>();
        CompletableFuture.supplyAsync(()->{
            try {
                Thread.sleep(5000);
                log.info("Slow task executed");
                return "Task finished";
            } catch (InterruptedException e) {
                e.printStackTrace();
                return "Task exception";
            }
        }).whenCompleteAsync((result, throwable) -> deferredResult.setResult(result));
        log.info("Servlet thread released");

        return deferredResult;
    }

  返回DeferredResult和返回Callable有什么区别?不同的是返回DeferredResult的线程是由我们管理。创建一个线程并将结果set到DeferredResult是由我们自己来做的。

  用completablefuture创建一个异步任务。这将创建一个新的线程,在那里我们的长时间运行的任务将被执行。也就是在这个线程中,我们将set结果到DeferredResult并返回。

  是在哪个线程池中我们取回这个新的线程?默认情况下,在completablefuture的supplyasync方法将在forkjoin池运行任务。如果你想使用一个不同的线程池,你可以通过传一个executor到supplyasync方法:

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

注意:Callable和Deferredresult做的是同样的事情——释放容器线程,在另一个线程上异步运行长时间的任务。不同的是谁管理执行任务的线程。

4.3、 WebAsyncTask

1》常规调用

  查看WebAsyncTask,有说明:Holder for a {@link Callable}, a timeout value, and a task executor.其实是一个Callable。

示例

    @RequestMapping(value="/longtimetask", method = RequestMethod.GET)
    public WebAsyncTask longTimeTask(){
        System.out.println("/longtimetask被调用 thread id is : " + Thread.currentThread().getId());
        Callable<String> callable = new Callable<String>() {
            public String call() throws Exception {
                Thread.sleep(3000); //假设是一些长时间任务
                System.out.println("执行成功 thread id is : " + Thread.currentThread().getId());
                return "ok";
            }
        };
        return new WebAsyncTask(callable);
    } 

  事实上,直接返回Callable<String>都是可以的,但这里包装了一层,以便做后面提到的“超时处理”。和前一个方案的差别在于这个Callable的call方法并不是我们直接调用的,而是在longTimeTask返回后,由Spring MVC用一个工作线程来调用,执行,打印出来的结果:

/longtimetask被调用 thread id is : 24
执行成功 thread id is : 38

2》超时处理

  如果“长时间处理任务”一直没返回,那也不应该让客户端无限等下去,需要服务端终结,即“超时”处理。如图:

007-优化web请求三-异步调用【WebAsyncTask】第11张

  “超时处理线程”和“回调处理线程”可能都是线程池中的某个线程,我为了清晰点把它们分开画而已。

    @RequestMapping(value="/longtimetaskTimeout", method = RequestMethod.GET)
    public WebAsyncTask longtimetaskTimeout(){
        System.out.println("/longtimetask被调用 thread id is : " + Thread.currentThread().getId());
        Callable<String> callable = new Callable<String>() {
            public String call() throws Exception {
                Thread.sleep(3000); //假设是一些长时间任务
                System.out.println("执行成功 thread id is : " + Thread.currentThread().getId());
                return "ok";
            }
        };
        WebAsyncTask webAsyncTask = new WebAsyncTask(2000,callable);
        webAsyncTask.onTimeout(()->{
            System.out.println("执行超时 thread id is :" + Thread.currentThread().getId());
            return "执行超时";
        });
        return webAsyncTask;
    }

  这就是前面提到的为什么Callable还要外包一层的缘故,给WebAsyncTask设置一个超时回调,即可实现超时处理,在这个例子中,正常处理需要3秒钟,而超时设置为2秒,所以肯定会出现超时

返回值

/longtimetask被调用 thread id is : 23
执行超时 thread id is :24

3》综合示例

自定义线程池

@Configuration
public class TaskConfiguration {
    @Bean("taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(5);
        taskExecutor.setMaxPoolSize(10);
        taskExecutor.setQueueCapacity(10);
        taskExecutor.setThreadNamePrefix("asyncTask");
        return taskExecutor;
    }
}

使用

    @GetMapping("/threadPool")
    public WebAsyncTask<String> asyncTaskThreadPool() {
        return new WebAsyncTask<>(10 * 1000L, executor,
                () -> {
                    out.println(format("异步工作线程:%s", currentThread().getName()));
                    return asyncService.generateUUID();
                });
    }

超时、异常综合示例

007-优化web请求三-异步调用【WebAsyncTask】第4张007-优化web请求三-异步调用【WebAsyncTask】第13张
@RestController
public class WebAsyncTaskController {
    private final WebAsyncService asyncService;
    private final static String ERROR_MESSAGE = "Task error";
    private final static String TIME_MESSAGE = "Task timeout";
    @Autowired
    @Qualifier("taskExecutor")
    private ThreadPoolTaskExecutor executor;

    @Autowired
    public WebAsyncTaskController(WebAsyncService asyncService) {
        this.asyncService = asyncService;
    }
    @GetMapping("/completion")
    public WebAsyncTask<String> asyncTaskCompletion() {
        // 打印处理线程名
        out.println(format("请求处理线程:%s", currentThread().getName()));

        // 模拟开启一个异步任务,超时时间为10s
        WebAsyncTask<String> asyncTask = new WebAsyncTask<>(10 * 1000L, () -> {
            out.println(format("异步工作线程:%s", currentThread().getName()));
            // 任务处理时间5s,不超时
            sleep(5 * 1000L);
            return asyncService.generateUUID();
        });

        // 任务执行完成时调用该方法
        asyncTask.onCompletion(() -> out.println("任务执行完成"));
        out.println("继续处理其他事情");
        return asyncTask;
    }

    @GetMapping("/exception")
    public WebAsyncTask<String> asyncTaskException() {
        // 打印处理线程名
        out.println(format("请求处理线程:%s", currentThread().getName()));

        // 模拟开启一个异步任务,超时时间为10s
        WebAsyncTask<String> asyncTask = new WebAsyncTask<>(10 * 1000L, () -> {
            out.println(format("异步工作线程:%s", currentThread().getName()));
            // 任务处理时间5s,不超时
            sleep(5 * 1000L);
            throw new Exception(ERROR_MESSAGE);
        });

        // 任务执行完成时调用该方法
        asyncTask.onCompletion(() -> out.println("任务执行完成"));
        asyncTask.onError(() -> {
            out.println("任务执行异常");
            return ERROR_MESSAGE;
        });

        out.println("继续处理其他事情");
        return asyncTask;
    }

    @GetMapping("/timeout")
    public WebAsyncTask<String> asyncTaskTimeout() {
        // 打印处理线程名
        out.println(format("请求处理线程:%s", currentThread().getName()));

        // 模拟开启一个异步任务,超时时间为10s
        WebAsyncTask<String> asyncTask = new WebAsyncTask<>(10 * 1000L, () -> {
            out.println(format("异步工作线程:%s", currentThread().getName()));
            // 任务处理时间5s,不超时
            sleep(15 * 1000L);
            return TIME_MESSAGE;
        });

        // 任务执行完成时调用该方法
        asyncTask.onCompletion(() -> out.println("任务执行完成"));
        asyncTask.onTimeout(() -> {
            out.println("任务执行超时");
            return TIME_MESSAGE;
        });

        out.println("继续处理其他事情");
        return asyncTask;
    }
}
View Code

4.4、@Async

参看:https://www.cnblogs.com/bjlhx/p/10364385.html 

五、Callable、DeferredResult、WebAsyncTask、Async对比、

 CallableWebAsyncTaskDeferredResultAsync
针对问题点异步请求处理异步请求处理异步请求处理异步方法
目标释放容器线程释放容器线程释放容器线程服务线程内多线程执行
拦截器 CallableProcessingInterceptor  DeferredResultProcessingInterceptor 
超时拦截器TimeoutCallableProcessingInterceptor TimeoutDeferredResultProcessingInterceptor 

常用类:

NoSupportAsyncWebRequest.java
  不支持异步处理模式的web请求
DeferredResultProcessingInterceptor.java
  DeferredResult处理过程拦截器
  在start async前,超时后/异步处理完成后/网络超时后触发拦截
DeferredResultProcessingInterceptorAdapter.java
  抽象类实现DeferredResultProcessingInterceptor,做空实现
DeferredResultInterceptorChain.java
  调用DeferredResultProcessingInterceptor的辅助类
DeferredResult.java
  递延结果,在两个线程中传递的对象结果
  实现Comparable接口以保证加入PriorityQueue队列的正确顺序
CallableProcessingInterceptor.java
  Callable拦截器
CallableProcessingInterceptorAdapter.java
  抽象类实现CallableProcessingInterceptor接口,空实现
CallableInterceptorChain.java
  调用CallableProcessingInterceptor的辅助类
TimeoutCallableProcessingInterceptor.java
  继承CallableProcessingInterceptorAdapter
  实现超时处理方法
TimeoutDeferredResultProcessingInterceptor.java
  继承DeferredResultProcessingInterceptorAdapter
  实现超时处理方法
WebAsyncTask.java
  web异步任务
  包含一个Callable类,一个超时时间,一个任务执行着或名字
WebAsyncUtils.java
  实现getAsyncManager
  实现createAsyncWebRequest
WebAsyncManager.java
  对Callables和DeferredResults启动的管理,包括拦截器的注入,Excutor的注入等
  异步处理的入口类

免责声明:文章转载自《007-优化web请求三-异步调用【WebAsyncTask】》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇ThreeJs 基础入门JAVA基于图片相似性算法实现以图搜图样例下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

iOS进阶之多线程

多线程 注意:iOS关于UI的刷新和添加必须在主线程中操作! pthread的创建方法: pthread_t pthread; //第一个参数 线程指针 //第二个参数 线程的一些属性 //第三个参数 函数指针 用于执行方法 //第四个参数 线程中的传值 pthread_create(&pt...

20200311 5. Response

5. Response 响应( response)对象封装了从服务器返回到客户端的所有信息。在 HTTP 协议中,从服务器传输到客户端 的信息通过 HTTP 头信息或响应的消息体。 5.1 缓冲 Servlet 容器允许但不必为了提高效率而缓冲到客户端的输出。典型的服务器默认都是缓冲的,但允许 servlet 指定缓冲参数。 ServletResponse...

c#实现多线程代码例子

相信大家都有用过网际快车等下载资源的经历,它里面是可以设置线程数的(近年版本默认是10,曾经默认是5)。它会将文件分成与线程数相同的部分,然后每个线程下载自己的那一部分,这样下载效率就有可能提高。相信大家都有加多线程数,提升下载效率的经历。但细心的用户会发现,在带宽一定的情况下,并不是线程越多,速度越快,而是在某一点达到峰值。在C#中用多线程并不难实现。它...

WPF系列学习

1:WPF最小化到系统托盘 2:WPF程序单例运行 3:WPF中三种异常捕获:UI线程异常、非UI线程异常、Task线程异常 在窗体放一个按钮在单击事件执行如下代码来模拟。 private void Button_Click(objectsender, RoutedEventArgs e) { //t...

python基础整理5——多进程多线程和协程

进程与线程 1.进程 我们电脑的应用程序,都是进程,假设我们用的电脑是单核的,cpu同时只能执行一个进程。当程序处于I/O阻塞的时候,CPU如果和程序一起等待,那就太浪费了,cpu会去执行其他的程序,此时就涉及到切换,切换前要保存上一个程序运行的状态,才能恢复,所以就需要有个东西来记录这个东西,就可以引出进程的概念了。 进程就是一个程序在一个数据集上的一次...

Jprofile解析dump文件使用详解

1 Jprofile简介 官网 下载对应的系统版本即可 性能查看工具JProfiler,可用于查看java执行效率,查看线程状态,查看内存占用与内存对象,还可以分析dump日志. 2 功能简介 选择attach to a locally running jvm 选择需要查看运行的jvm,双击或者点击start 等待进度完成,弹出模式选择 I...