Loading...

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方法,响应客户端。