Spring MVC 教程-异步处理请求

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

1、本篇内容

本文让大家掌握springmvc中异步处理请求,特别牛逼的一个功能,大家一定要掌握。

2、看段代码,分析问题

    @ResponseBody
    @RequestMapping("/async/m1.do")
    public String m1() throws InterruptedException {
        long st = System.currentTimeMillis();
        System.out.println("主线程:" + Thread.currentThread() + "," + st + ",开始");
        //休眠3秒,模拟耗时的业务操作
        TimeUnit.SECONDS.sleep(3);
        long et = System.currentTimeMillis();
        System.out.println("主线程:" + Thread.currentThread() + "," + st + ",结束,耗时(ms):" + (et - st));
        return "ok";
    }

这段代码很简单

  • 这段代码是springmvc提供的一个接口
  • 内部休眠了3秒钟,用来模拟耗时的操作
  • 方法内部有2条日志(日志中包含了当前线程、开始时间、结束时间、耗时)

浏览器中访问下这个接口,效果如下,可以看到接口耗时3s左右。

控制台输出

    主线程:Thread[http-nio-8080-exec-1,5,main],1624889293055,开始
    主线程:Thread[http-nio-8080-exec-1,5,main],1624889293055,结束,耗时(ms):3002

从输出中,我们可以看出,这个接口从开始到结束都是由tomcat中的线程来处理用户请求的,也就是说,3秒这段时间内,tomcat中的一个线程会被当前请求一直占用了则,tomcat线程是有最大值的,默认情况下好像是75,那么问题来了。

当3秒之内,来的请求数量超过了tomcat最大线程数的时候,其他请求就无法处理了,而此时tomcat中这些线程都处理sleep 3s的休眠状态,cpu此时没活干,此时就会造成机器没活干,但是呢又不能处理新的请求,这就是坑啊,浪费资源,怎么办呢?

遇到这种场景的,也就是说接口内部比价耗时,但是又不能充分利用cpu的,我们可以采用异步的方式来处理请求,过程如下:

tomcat线程,将请求转发给我们自定义的子线程去处理这个请求,然后tomcat就可以继续去接受新的请求了。

3、springmvc中异步处理

主要有3个大的步骤。

step1:servlet开启异步处理支持

web.xml中开启servlet异步支持

step2:Filter中添加异步支持

如果我们的异步请求需要经过Filter的,那么需要在web.xml对这个Filter添加异步支持.

step3:接口返回值为DeferredResult

这个步骤中细节比较多,当需要异步响应请求的时候,返回值需要为DeferredResult,具体参考下面案例代码,详细信息都在注释中了,大家注意看注释。

  • 第1步:创建DeferredResult<返回值类型>(超时时间[毫秒],超时回调的代码)
  • 第2步:在子线程中异步处理业务,调用DeferredResult的setResult方法,设置最终返回到客户端的结果,此方法调用以后,客户端将接收到返回值,然后响应过程请求就结束了
  • 第3步:将DefaultResult作为方法返回值
    /**
     * 使用springmvc的异步功能,业务处理放在异步线程中执行
     *
     * @param timeout 异步处理超时时间(毫秒)
     * @return
     */
    @ResponseBody
    @RequestMapping("/async/m2/{timeout}.do")
    public DeferredResult<String> m2(@PathVariable("timeout") long timeout) {
        long st = System.currentTimeMillis();
        System.out.println("主线程:" + Thread.currentThread() + "," + st + ",开始");
        /**
         * 1、创建DeferredResult<返回值类型>(超时时间[毫秒],超时回调的代码)
         */
        DeferredResult<String> result = new DeferredResult<String>(timeout, () -> {
            System.out.println("超时了");
            return "timeout";
        });
        //2、异步处理业务,
        new Thread(() -> {
            //开启一个异步线程,在异步线程中进行业务处理操作
            try {
                TimeUnit.SECONDS.sleep(3);
                //3、调用DeferredResult的setResult方法,设置最终返回到客户端的结果,此方法调用以后,客户端将接收到返回值
                result.setResult("ok");
            } catch (InterruptedException e) {
                result.setResult("发生异常了:" + e.getMessage());
            }
        }).start();
        long et = System.currentTimeMillis();
        System.out.println("主线程:" + Thread.currentThread() + "," + st + ",结束,耗时(ms):" + (et - st));
        //3、将DefaultResult作为方法返回值
        return result;
    }

上面的m2方法个timeout参数,调用者通过这个参数来指定接口的超时时间,未超时的情况下,也就是说timeout大于3秒的时候,此时会输出ok,否则将出现超时,此时会将DeferredResult构造器第2个参数的执行结果作为最终的响应结果,即会向客户端输出timeout。

使用建议:案例开启了一个新的子线程来执行业务操作,生产环境中,建议大家采用线程池的方式,效率更高。

下面我们来通过2个case来模拟下这个接口超时和正常的结果。

4、模拟非超时请求

当timeout大于3秒时,才不会出现超时,此时我们传递4000毫秒来试试

控制台输出如下,可以看到主线程瞬间就结束了。

    主线程:Thread[http-nio-8080-exec-6,5,main],1624891886020,开始
    主线程:Thread[http-nio-8080-exec-6,5,main],1624891886020,结束,耗时(ms):0

5、模拟超时请求

当timeout小于3秒会出现超时,此时我们传递1000毫秒来试试

控制台输出如下,输出了超时信息,且通过前两行输出看出主线程瞬间就结束了,不会被请求阻塞。

    主线程:Thread[http-nio-8080-exec-1,5,main],1624892109695,开始
    主线程:Thread[http-nio-8080-exec-1,5,main],1624892109695,结束,耗时(ms):0
    超时了

6、总结

当接口中有大量的耗时的操作,且这些耗时的操作让线程处于等待状态的时候,此时为了提升系统的性能,可以将接口调整为异步处理请求的方式。

7、案例代码

    git地址:https://gitee.com/javacode2018/springmvc-series

  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值