SpringMVC的异步

SpringMVC的异步

最近接触了springMVC的异步模式,总结下来有两个优点:

第一,当然是节约tomcat容器的线程

第二,可以利用异步超时,起到一定的超时降级保护

注意:在Controller中使用时,一定要注意做好接口的线程池隔离,让慢的接口使用固定数量的线程池, 否则从tomcat减少的线程会转移到应用里,导致拥塞,在部分接口下游异常的情况的情况下,会出现影响正常接口的服务.

关于Spring MVC的异步

同步接口在请求处理过程中一直处于阻塞状态,而异步接口可以启用后台线程去处理耗时任务。基本的使用场景:

  • 高并发场景

  • 高耗时场景

SpringMVC提供的几种异步实现方案:

  • Callable 提供的带有返回值的并发操作

  • WebAsyncTask 对Callable的封装处理

  • @Async

  • DeferredResult

Callable

就是最简单的线程运行,然后获取返回值的模式。优点是简单,缺点是不能中途取消,只有结合FutureTask才能够提供一定的控制。但是,也不能提供异步通知的方式。

/**
 * @author qiyu
 * @date 2020-07-30 20:03
 */
@Slf4j
@RestController
public class AsyncController {

    @GetMapping("/test")
    public String test() throws InterruptedException {
        log.info("主线程开始=====>"+ Thread.currentThread().getName());
        Thread.sleep(30000);
        log.info("主线程结束=====>"+ Thread.currentThread().getName());
        return "success";
    }


    @GetMapping("/testAsync")
    public Callable<String> testAsync() throws InterruptedException {
        log.info("主线程开始=====>"+ Thread.currentThread().getName());
        Callable<String> callable = () -> {
            log.info("异步线程开始=====>" + Thread.currentThread().getName());
            Thread.sleep(30000);
            log.info("异步线程结束=====>" + Thread.currentThread().getName());
            return "success";
        };
        log.info("主线程结束=====>"+ Thread.currentThread().getName());
        return callable;
    }
}

WebAsyncTask 实例

WebAsyncTask : 在构造时写入Callable主要业务逻辑
WebAsyncTask.onCompletion(Runnable) :在当前任务执行结束以后,无论是执行成功还是异常中止,onCompletion的回调最终都会被调用
WebAsyncTask.onError(Callable>) :当异步任务抛出异常的时候,onError()方法即会被调用
WebAsyncTask.onTimeout(Callable>) :当异步任务发生超时的时候,onTimeout()方法即会被调用

WebAsyncTask类是Spring提供的一步任务处理类。

WebAsyncTaskCallable的升级版

@RequestMapping("/async")
    @ResponseBody
    public WebAsyncTask<String> asyncTask(){
        // 1000 为超时设置
        WebAsyncTask<String> webAsyncTask = new WebAsyncTask<String>(1000,new Callable<String>(){
            @Override
            public String call() throws Exception {
                //业务逻辑处理
                Thread.sleep(5000);
                String message = "username:wangbinghua";
                return message;
            }
        });
        webAsyncTask.onCompletion(new Runnable() {
            @Override
            public void run() {
                System.out.println("调用完成");
            }
        });
        webAsyncTask.onTimeout(new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("业务处理超时");
                return "<h1>Time Out</h1>";
            }
        });

        return webAsyncTask;
    }

@Async实例

含义

1,在方法上使用该@Async注解,申明该方法是一个异步任务;

2,在类上面使用该@Async注解,申明该类中的所有方法都是异步任务;

3,使用此注解的方法的类对象,必须是spring管理下的bean对象;

4,要想使用异步任务,需要在主类上开启异步配置,即,配置上@EnableAsync注解;

用法

在Spring中启用@Async:

    1,@Async注解在使用时,如果不指定线程池的名称,则使用Spring默认的线程池,Spring默认的线程池为**SimpleAsyncTaskExecutor**。

    2,方法上一旦标记了这个@Async注解,当其它线程调用这个方法时,就会开启一个新的子线程去异步处理该业务逻辑。

例子

3.1,启动类中增加@EnableAsync

以Spring boot 为例,启动类中增加@EnableAsync:

@EnableAsync@SpringBootApplicationpublic class ManageApplication {    //...}

3.2,方法上加@Async注解:

@Componentpublic class MyAsyncTask {     @Async    public void asyncCpsItemImportTask(Long platformId, String jsonList){        //...具体业务逻辑    }}

3.3,默认线程池的缺陷:

    上面的配置会启用默认的线程池/执行器,异步执行指定的方法。

    Spring默认的线程池的默认配置:

    默认核心线程数:8,        最大线程数:Integet.MAX_VALUE,    队列使用LinkedBlockingQueue,    容量是:Integet.MAX_VALUE,    空闲线程保留时间:60s,    线程池拒绝策略:AbortPolicy。

    从最大线程数的配置上,相信你也看到问题了:**并发情况下,会无限创建线程。。。**

3.4,默认线程池–自定义配置参数:

    默认线程池的上述缺陷如何解决:

    答案是,自定义配置参数就可以了。

spring:
  task:
    execution:
      pool:
        max-size: 6
        core-size: 3
        keep-alive: 3s
        queue-capacity: 1000
        thread-name-prefix: name

实现原理

@Async的原理概括:

@Async 的原理是通过 Spring AOP 动态代理 的方式来实现的。

    Spring容器启动初始化bean时,判断类中是否使用了@[Async](https://so.csdn.net/so/search?q=Async\&spm=1001.2101.3001.7020 "Async")注解,如果使用了则为其创建切入点和切入点处理器,根据切入点创建代理,

    在线程调用@Async注解标注的方法时,会调用代理,执行切入点处理器invoke方法,将方法的执行提交给线程池中的另外一个线程来处理,从而实现了异步执行。

    所以,需要注意的一个错误用法是,如果a方法调用它同类中的标注@Async的b方法,是不会异步执行的,因为从a方法进入调用的都是该类对象本身,不会进入代理类。

因此,相同类中的方法调用带@Async的方法是无法异步的,这种情况仍然是同步。

DeferredResult实例

DeferredResultCallable实现功能类型,都是异步返回,只不过Callable不能直接设置超时时间,还需要和FutureTask配合使用

@Controller
public class DeferedResultController {


    private ConcurrentLinkedDeque<DeferredResult<String>> deferredResults =
            new ConcurrentLinkedDeque<DeferredResult<String>>();


    @RequestMapping("/getResult")
    @ResponseBody
    public DeferredResult<String> getDeferredResultController(){

        //设置 5秒就会超时
        final DeferredResult<String> stringDeferredResult = new DeferredResult<String>(1000);
        
        //将请求加入到队列中
        deferredResults.add(stringDeferredResult);

        final String message = "{username:wangbinghua}";

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.submit(new Runnable() {
            @Override
            public void run() {
          
                try {
                    Thread.sleep(1010);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //业务处理
                System.out.println("业务处理");
                stringDeferredResult.setResult(message);
            }
        });


        //setResult完毕之后,调用该方法
        stringDeferredResult.onCompletion(new Runnable() {
            @Override
            public void run() {
                System.out.println("异步调用完成");
                //响应完毕之后,将请求从队列中去除掉
                deferredResults.remove(stringDeferredResult);
            }
        });

        stringDeferredResult.onTimeout(new Runnable() {
            @Override
            public void run() {
                System.out.println("业务处理超时");
                stringDeferredResult.setResult("error:timeOut");
            }
        });
        return stringDeferredResult;
    }

    //开启线程定时扫描队列,响应客户端
    @Scheduled(fixedRate = 1000)
    public void scheduleResult(){
        System.out.println(new Date());
        for(int i = 0;i < deferredResults.size();i++){
            DeferredResult<String> deferredResult = deferredResults.getFirst();
            deferredResult.setResult("result:" + i);
        }
    }
}

说明

在Spring Mvc的控制层(springmvc层框架中)中,只要有一个用户请求便会实例化一个DeferedResult对象,然后返回该对象,然后就会释放TOMCAT的线程,从而完成客户端响应。只要DeferedResult对象不设置result响应的内容,则控制层(springMVC层)的容器主线程在响应客户端上就会发生阻塞。

因为SpringMVC只会实例化一个Controller对象,无论有多少个用户请求,在堆上只有一个Controller对象,因此可以添加一个成员变量List,将这些用户请求的DeferedResult对象存放到List中,然后启动一个定时线程扫描list,从而依次执行setResult方法,响应客户端。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当使用Spring MVC进行异步文件上传时,需要进行以下步骤: 1. 在Spring MVC的配置文件中启用异步支持,可以使用<mvc:annotation-driven>元素或@EnableWebMvc注解来完成。 2. 创建一个控制器方法来处理文件上传请求。该方法应该使用@RequestParam注解来接收文件,并且需要使用@RequestBody注解来指定处理请求体的方式为异步。 3. 创建一个异步任务来处理文件上传操作。可以使用Spring异步任务支持,即@Async注解来完成。在异步任务中,可以使用Spring的ResourceUtils类将文件保存到指定的位置。 4. 返回一个异步结果对象来表示文件上传的状态。可以使用DeferredResult或ListenableFuture等Spring提供的异步结果对象来完成。 下面是一个示例控制器方法的代码: ``` @RequestMapping(value = "/uploadFile", method = RequestMethod.POST) @ResponseBody public DeferredResult<String> handleFileUpload(@RequestParam("file") MultipartFile file) { DeferredResult<String> deferredResult = new DeferredResult<>(); AsyncTask asyncTask = new AsyncTask(file, deferredResult); asyncTask.execute(); return deferredResult; } ``` 在该示例代码中,handleFileUpload方法使用DeferredResult来表示异步处理的结果,然后创建一个AsyncTask对象来处理文件上传操作。AsyncTask类是一个自定义的异步任务类,其中包含一个execute方法,用于执行文件上传操作。在该方法中,使用ResourceUtils类将文件保存到指定的位置,并最终将文件上传的结果通知给DeferredResult对象。 请注意,在使用Spring MVC进行文件上传时,需要确保上传的文件大小不超过服务器所允许的最大限制。可以通过在Spring MVC的配置文件中配置multipartResolver来设置上传文件的最大大小。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值