前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【云+社区年度征文】几种SpringMvc的 异步编程了解下?

【云+社区年度征文】几种SpringMvc的 异步编程了解下?

原创
作者头像
java金融
修改2020-12-21 18:06:55
7880
修改2020-12-21 18:06:55
举报
文章被收录于专栏:java金融java金融

引言

说到异步大家肯定首先会先想到同步。我们先来看看什么是同步?

所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。

简单来说,同步就是必须一件一件事做,等前一件做完了才能做下一件事。异步:异步就相反,调用在发出之后,这个调用就直接返回了,不需要等结果。

浏览器同步

浏览器发起一个request然后会一直待一个响应response,在这期间里面它是阻塞的。比如早期我们在我们在逛电商平台的时候买东西我们打开一个商品的页面,大致流程是不是可能是这样,每次打开一个页面都是由一个线程从头到尾来处理,这个请求需要进行数据库的访问需要把商品价格库存啥的返回页面,还需要去调用第三方接口,比如优惠券接口等我们只有等到这些都处理完成后这个线程才会把结果响应给浏览器,在这等结果期间这个线程只能一直在干等着啥事情也不能干。这样的话是不是会有有一定的性能问题。大致的流程如下:

在这里插入图片描述
在这里插入图片描述

浏览器异步

为了解决上面同步阻塞的问题,再Servlet3.0发布后,提供了一个新特性:异步处理请求。比如我们还是进入商品详情页面,这时候这个前端发起一个请求,然后会有一个线程来执行这个请求,这个请求需要去数据库查询库存、调用第三方接口查询优惠券等。这时候这个线程就不用干等着呢。它的任务到这就完成了,又可以执行下一个任务了。等查询数据库和第三方接口查询优惠券有结果了,这时候会有一个新的线程来把处理结果返回给前端。这样的话线程的工作量是不超级饱和,需要不停的干活,连休息的机会都不给了。

在这里插入图片描述
在这里插入图片描述
  • 这个异步是纯后端的异步,对前端是无感的,异步也并不会带来响应时间上的优化,原来该执行多久照样还是需要执行多久。但是我们的请求线程(Tomcat 线程)为异步servlet之后,我们可以立即返回,依赖于业务的任务用业务线程来执行,也就是说,Tomcat的线程可以立即回收,默认情况下,Tomcat的核心线程是10,最大线程数是200,我们能及时回收线程,也就意味着我们能处理更多的请求,能够增加我们的吞吐量,这也是异步Servlet的主要作用。 下面我们就来看看Spring mvc 的几种异步方式吧 https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-async
    在这里插入图片描述
    在这里插入图片描述
    在这个之前我们还是先简单的回顾下Servlet 3.1的异步:
  • 客户端(浏览器、app)发送一个请求
  • Servlet容器分配一个线程来处理容器中的一个servlet
  • servlet调用request.startAsync()开启异步模式,保存AsyncContext, 然后返回。
  • 这个servlet请求线程以及所有的过滤器都可以结束,但其响应(response)会等待异步线程处理结束后再返回。
  • 其他线程使用保存的AsyncContext来完成响应
  • 客户端收到响应
    在这里插入图片描述
    在这里插入图片描述
Callable
代码语言:txt
复制
     /**  公众号:java金融
     * 使用Callable
     * @return
     */
    @GetMapping("callable")
    public Callable<String> callable() {
        System.out.println(LocalDateTime.now().toString() + "--->主线程开始");
        Callable<String> callable = () -> {
            String result = "return callable";
            // 执行业务耗时 5s
            Thread.sleep(5000);
            System.out.println(LocalDateTime.now().toString() + "--->子任务线程("+Thread.currentThread().getName()+")");
            return result;
        };
        System.out.println(LocalDateTime.now().toString() + "--->主线程结束");
        return callable;
    }
       public static String doBusiness() {
        // 执行业务耗时 10s
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return UUID.randomUUID().toString();
    }
    
  • 控制器先返回一个Callable对象
  • Spring MVC开始进行异步处理,并把该Callable对象提交给另一个独立线程的执行器TaskExecutor处理
  • DispatcherServlet和所有过滤器都退出Servlet容器线程,但此时方法的响应对象仍未返回
  • Callable对象最终产生一个返回结果,此时Spring MVC会重新把请求分派回Servlet容器,恢复处理
  • DispatcherServlet再次被调用,恢复对Callable异步处理所返回结果的处理 上面就是Callable的一个执行流程,下面我们来简单的分析下源码,看看是怎么实现的: 我们知道SpringMvc是可以返回json格式数据、或者返回视图页面(html、jsp)等,SpringMvc是怎么实现这个的呢?最主要的一个核心类就是org.springframework.web.method.support.HandlerMethodReturnValueHandler 我们来看看这个类,这个类就是一个接口,总共就两个方法;
代码语言:txt
复制
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
上面这个我们的请求是返回Callable<String> 这样一个结果的,我们会根据这个返回的类型去找所有实现了HandlerMethodReturnValueHandler 这个接口的实现类,最终我们会根据返回类型通过supportsReturnType这个实现的方法找到一个对应的HandlerMethodReturnValueHandler 实现类,我们根据返回类型是Callable然后就找到了实现类CallableMethodReturnValueHandler。
在这里插入图片描述
在这里插入图片描述

开启异步线程的话也就是在handleReturnValue这个方法里面了,感兴趣的大家可以动手去debug下还是比较好调试的。

CompletableFuture 和ListenableFuture
代码语言:txt
复制
   @GetMapping("completableFuture")
    public CompletableFuture<String> completableFuture() {
        // 线程池一般不会放在这里,会使用static声明,这只是演示
        ExecutorService executor = Executors.newCachedThreadPool();
        System.out.println(LocalDateTime.now().toString() + "--->主线程开始");
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(IndexController::doBusiness, executor);
        System.out.println(LocalDateTime.now().toString() + "--->主线程结束");
        return completableFuture;
    }

    @GetMapping("listenableFuture")
    public ListenableFuture<String> listenableFuture() {
        // 线程池一般不会放在这里,会使用static声明,这只是演示
        ExecutorService executor = Executors.newCachedThreadPool();
        System.out.println(LocalDateTime.now().toString() + "--->主线程开始");
        ListenableFutureTask<String> listenableFuture = new ListenableFutureTask<>(()->   doBusiness());
        executor.execute(listenableFuture);
        System.out.println(LocalDateTime.now().toString() + "--->主线程结束");
        return listenableFuture;
    }

注:这种方式记得不要使用内置的不要使用内置的 ForkJoinPool线程池,需要自己创建线程池否则会有性能问题

WebAsyncTask
代码语言:txt
复制
 @GetMapping("asynctask")
    public WebAsyncTask asyncTask() {
        SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
        System.out.println(LocalDateTime.now().toString() + "--->主线程开始");
        WebAsyncTask<String> task = new WebAsyncTask(1000L, executor, ()-> doBusiness());
        task.onCompletion(()->{
            System.out.println(LocalDateTime.now().toString() + "--->调用完成");
        });
        task.onTimeout(()->{
            System.out.println("onTimeout");
            return "onTimeout";
        });
        System.out.println(LocalDateTime.now().toString() + "--->主线程结束");
        return task;
    }
DeferredResult
代码语言:txt
复制
    @GetMapping("deferredResult")
    public DeferredResult<String> deferredResult() {
        System.out.println(LocalDateTime.now().toString() + "--->主线程("+Thread.currentThread().getName()+")开始");
        DeferredResult<String> deferredResult = new DeferredResult<>();
        CompletableFuture.supplyAsync(()-> doBusiness(), Executors.newFixedThreadPool(5)).whenCompleteAsync((result, throwable)->{
            if (throwable!=null) {
                deferredResult.setErrorResult(throwable.getMessage());
            }else {
                deferredResult.setResult(result);
            }
        });
        // 异步请求超时时调用
        deferredResult.onTimeout(()->{
            System.out.println(LocalDateTime.now().toString() + "--->onTimeout");
        });
        // 异步请求完成后调用
        deferredResult.onCompletion(()->{
            System.out.println(LocalDateTime.now().toString() + "--->onCompletion");
        });
        System.out.println(LocalDateTime.now().toString() + "--->主线程("+Thread.currentThread().getName()+")结束");
        return deferredResult;
    }
  • 上面这几种异步方式都是会等到业务doBusiness执行完之后(10s)才会把response给到前端,执行请求的主线程会立即结束,响应结果会交给另外的线程来返回给前端。
  • 这种异步跟下面的这个所谓的假异步是不同的,这种情况是由主线程执行完成之后立马返回值(主线程)给前端,不会等个5s在返回给前端。
代码语言:txt
复制
    @GetMapping("call")
    public String call() {
       new Thread(new Runnable() {
           @Override
           public void run() {
               try {
                   Thread.sleep(5000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }).start();
        return "这是个假异步";
    }

这几种异步方式都跟返回Callable 差不多,都有对应的HandlerMethodReturnValueHandler 实现类,无非就是丰富了自己一些特殊的api、比如超时设置啥的,以及线程池的创建是谁来创建,执行流程基本都是一样的。

总结

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
    • 浏览器同步
    • 浏览器异步
      • Callable
        • CompletableFuture 和ListenableFuture
          • WebAsyncTask
            • DeferredResult
            • 总结
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档