精华内容
下载资源
问答
  • 一、Spring Boot中异步请求的使用1、异步请求与同步请求特点:可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对...

    一、Spring Boot中异步请求的使用

    1、异步请求与同步请求

    3a29b52495c2275d282b397d5098b32b.png

    特点:

    可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。

    一句话:增加了服务器对客户端请求的吞吐量(实际生产上我们用的比较少,如果并发请求量很大的情况下,我们会通过nginx把请求负载到集群服务的各个节点上来分摊请求压力,当然还可以通过消息队列来做请求的缓冲)。

    2、异步请求的实现

    方式一:Servlet方式实现异步请求

    @RequestMapping(value = "/email/servletReq", method = GET)  public void servletReq (HttpServletRequest request, HttpServletResponse response) {      AsyncContext asyncContext = request.startAsync();      //设置监听器:可设置其开始、完成、异常、超时等事件的回调处理      asyncContext.addListener(new AsyncListener() {          @Override          public void onTimeout(AsyncEvent event) throws IOException {              System.out.println("超时了...");              //做一些超时后的相关操作...          }          @Override          public void onStartAsync(AsyncEvent event) throws IOException {              System.out.println("线程开始");          }          @Override          public void onError(AsyncEvent event) throws IOException {              System.out.println("发生错误:"+event.getThrowable());          }          @Override          public void onComplete(AsyncEvent event) throws IOException {              System.out.println("执行完成");              //这里可以做一些清理资源的操作...          }      });      //设置超时时间      asyncContext.setTimeout(20000);      asyncContext.start(new Runnable() {          @Override          public void run() {              try {                  Thread.sleep(10000);                  System.out.println("内部线程:" + Thread.currentThread().getName());                  asyncContext.getResponse().setCharacterEncoding("utf-8");                  asyncContext.getResponse().setContentType("text/html;charset=UTF-8");                  asyncContext.getResponse().getWriter().println("这是异步的请求返回");              } catch (Exception e) {                  System.out.println("异常:"+e);              }              //异步请求完成通知              //此时整个请求才完成              asyncContext.complete();          }      });      //此时之类 request的线程连接已经释放了      System.out.println("主线程:" + Thread.currentThread().getName());  }

    方式二:使用很简单,直接返回的参数包裹一层callable即可,可以继承WebMvcConfigurerAdapter类来设置默认线程池和超时处理

    @RequestMapping(value = "/email/callableReq", method = GET)  @ResponseBody  public Callable callableReq () {      System.out.println("外部线程:" + Thread.currentThread().getName());      return new Callable() {          @Override          public String call() throws Exception {              Thread.sleep(10000);              System.out.println("内部线程:" + Thread.currentThread().getName());              return "callable!";          }      };  }  @Configuration  public class RequestAsyncPoolConfig extends WebMvcConfigurerAdapter {  @Resource  private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;  @Override  public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {      //处理 callable超时      configurer.setDefaultTimeout(60*1000);      configurer.setTaskExecutor(myThreadPoolTaskExecutor);      configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());  }  @Bean  public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {      return new TimeoutCallableProcessingInterceptor();  }}

    方式三:和方式二差不多,在Callable外包一层,给WebAsyncTask设置一个超时回调,即可实现超时处理

    @RequestMapping(value = "/email/webAsyncReq", method = GET)    @ResponseBody    public WebAsyncTask webAsyncReq () {        System.out.println("外部线程:" + Thread.currentThread().getName());        Callable result = () -> {            System.out.println("内部线程开始:" + Thread.currentThread().getName());            try {                TimeUnit.SECONDS.sleep(4);            } catch (Exception e) {                // TODO: handle exception            }            logger.info("副线程返回");            System.out.println("内部线程返回:" + Thread.currentThread().getName());            return "success";        };        WebAsyncTask wat = new WebAsyncTask(3000L, result);        wat.onTimeout(new Callable() {            @Override            public String call() throws Exception {                // TODO Auto-generated method stub                return "超时";            }        });        return wat;    }

    方式四:DeferredResult可以处理一些相对复杂一些的业务逻辑,最主要还是可以在另一个线程里面进行业务处理及返回,即可在两个完全不相干的线程间的通信。

    @RequestMapping(value = "/email/deferredResultReq", method = GET)    @ResponseBody    public DeferredResult deferredResultReq () {        System.out.println("外部线程:" + Thread.currentThread().getName());        //设置超时时间        DeferredResult result = new DeferredResult(60*1000L);        //处理超时事件 采用委托机制        result.onTimeout(new Runnable() {            @Override            public void run() {                System.out.println("DeferredResult超时");                result.setResult("超时了!");            }        });        result.onCompletion(new Runnable() {            @Override            public void run() {                //完成后                System.out.println("调用完成");            }        });        myThreadPoolTaskExecutor.execute(new Runnable() {            @Override            public void run() {                //处理业务逻辑                System.out.println("内部线程:" + Thread.currentThread().getName());                //返回结果                result.setResult("DeferredResult!!");            }        });       return result;    }

    二、Spring Boot中异步调用的使用

    1、介绍

    异步请求的处理。除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。

    2、使用方式(基于spring下)

    需要在启动类加入@EnableAsync使异步调用@Async注解生效

    在需要异步执行的方法上加入此注解即可@Async("threadPool"),threadPool为自定义线程池。

    代码略。。。就俩标签,自己试一把就可以了

    3、注意事项

    在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。所以最好我们来自定义一个线程池。

    调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。

    其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。下面会重点讲述。。

    4、什么情况下会导致@Async异步方法会失效?

    调用同一个类下注有@Async异步方法:

    在spring中像@Async和@Transactional、cache等注解本质使用的是动态代理,其实Spring容器在初始化的时候Spring容器会将含有AOP注解的类对象“替换”为代理对象(简单这么理解),那么注解失效的原因就很明显了,就是因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器,那么解决方法也会沿着这个思路来解决

    调用的是静态(static )方法

    调用(private)私有化方法

    5、解决4中问题1的方式(其它2,3两个问题自己注意下就可以了)

    将要异步执行的方法单独抽取成一个类,原理就是当你把执行异步的方法单独抽取成一个类的时候,这个类肯定是被Spring管理的,其他Spring组件需要调用的时候肯定会注入进去,这时候实际上注入进去的就是代理类了。

    其实我们的注入对象都是从Spring容器中给当前Spring组件进行成员变量的赋值,由于某些类使用了AOP注解,那么实际上在Spring容器中实际存在的是它的代理对象。那么我们就可以通过上下文获取自己的代理对象调用异步方法。

    @Controller@RequestMapping("/app")public class EmailController {    //获取ApplicationContext对象方式有多种,这种最简单,其它的大家自行了解一下    @Autowired    private ApplicationContext applicationContext;    @RequestMapping(value = "/email/asyncCall", method = GET)    @ResponseBody    public Map asyncCall () {        Map resMap = new HashMap();        try{            //这样调用同类下的异步方法是不起作用的            //this.testAsyncTask();            //通过上下文获取自己的代理对象调用异步方法            EmailController emailController = (EmailController)applicationContext.getBean(EmailController.class);            emailController.testAsyncTask();            resMap.put("code",200);        }catch (Exception e) {            resMap.put("code",400);            logger.error("error!",e);        }        return resMap;    }    //注意一定是public,且是非static方法    @Async    public void testAsyncTask() throws InterruptedException {        Thread.sleep(10000);        System.out.println("异步任务执行完成!");    }}

    开启cglib代理,手动获取Spring代理类,从而调用同类下的异步方法。首先,在启动类上加上@EnableAspectJAutoProxy(exposeProxy = true)注解。代码实现,如下:

    @Service@Transactional(value = "transactionManager", readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)public class EmailService {    @Autowired    private ApplicationContext applicationContext;    @Async    public void testSyncTask() throws InterruptedException {        Thread.sleep(10000);        System.out.println("异步任务执行完成!");    }    public void asyncCallTwo() throws InterruptedException {        //this.testSyncTask();//        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);//        emailService.testSyncTask();        boolean isAop = AopUtils.isAopProxy(EmailController.class);//是否是代理对象;        boolean isCglib = AopUtils.isCglibProxy(EmailController.class);  //是否是CGLIB方式的代理对象;        boolean isJdk = AopUtils.isJdkDynamicProxy(EmailController.class);  //是否是JDK动态代理方式的代理对象;        //以下才是重点!!!        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);        EmailService proxy = (EmailService) AopContext.currentProxy();        System.out.println(emailService == proxy ? true : false);        proxy.testSyncTask();        System.out.println("end!!!");    }}

    三、异步请求与异步调用的区别

    两者的使用场景不同,异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算和响应的任务,比如同步日志到kafka中做日志分析等。

    异步请求是会一直等待response相应的,需要返回结果给客户端的;而异步调用我们往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台自己慢慢跑就行,客户端不会关心。

    四、总结

    异步请求和异步调用的使用到这里基本就差不多了,有问题还希望大家多多指出。这边文章提到了动态代理,而spring中Aop的实现原理就是动态代理,后续会对动态代理做详细解读,还望多多支持哈~

    展开全文
  • 异步请求异步调用区别 两者的使用场景不同,异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算响应的任务,比如同步日志到kafka中做...

    异步请求与异步调用的区别

    两者的使用场景不同,异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算和响应的任务,比如同步日志到kafka中做日志分析等。

    异步请求是会一直等待response相应的,需要返回结果给客户端的;而异步调用我们往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台自己慢慢跑就行,客户端不会关心。

     

     

    1、异步请求的实现

    方式一:Servlet方式实现异步请求

      @RequestMapping(value = "/email/servletReq", method = GET)
      public void servletReq (HttpServletRequest request, HttpServletResponse response) {
          AsyncContext asyncContext = request.startAsync();
          //设置监听器:可设置其开始、完成、异常、超时等事件的回调处理
          asyncContext.addListener(new AsyncListener() {
              @Override
              public void onTimeout(AsyncEvent event) throws IOException {
                  System.out.println("超时了...");
                  //做一些超时后的相关操作...
              }
              @Override
              public void onStartAsync(AsyncEvent event) throws IOException {
                  System.out.println("线程开始");
              }
              @Override
              public void onError(AsyncEvent event) throws IOException {
                  System.out.println("发生错误:"+event.getThrowable());
              }
              @Override
              public void onComplete(AsyncEvent event) throws IOException {
                  System.out.println("执行完成");
                  //这里可以做一些清理资源的操作...
              }
          });
          //设置超时时间
          asyncContext.setTimeout(20000);
          asyncContext.start(new Runnable() {
              @Override
              public void run() {
                  try {
                      Thread.sleep(10000);
                      System.out.println("内部线程:" + Thread.currentThread().getName());
                      asyncContext.getResponse().setCharacterEncoding("utf-8");
                      asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
                      asyncContext.getResponse().getWriter().println("这是异步的请求返回");
                  } catch (Exception e) {
                      System.out.println("异常:"+e);
                  }
                  //异步请求完成通知
                  //此时整个请求才完成
                  asyncContext.complete();
              }
          });
          //此时之类 request的线程连接已经释放了
          System.out.println("主线程:" + Thread.currentThread().getName());
      }
    

    方式二:使用很简单,直接返回的参数包裹一层callable即可,可以继承WebMvcConfigurerAdapter类来设置默认线程池和超时处理

      @RequestMapping(value = "/email/callableReq", method = GET)
      @ResponseBody
      public Callable<String> callableReq () {
          System.out.println("外部线程:" + Thread.currentThread().getName());
    
          return new Callable<String>() {
    
              @Override
              public String call() throws Exception {
                  Thread.sleep(10000);
                  System.out.println("内部线程:" + Thread.currentThread().getName());
                  return "callable!";
              }
          };
      }
    
      @Configuration
      public class RequestAsyncPoolConfig extends WebMvcConfigurerAdapter {
    
      @Resource
      private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;
    
      @Override
      public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
          //处理 callable超时
          configurer.setDefaultTimeout(60*1000);
          configurer.setTaskExecutor(myThreadPoolTaskExecutor);
          configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());
      }
    
      @Bean
      public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {
          return new TimeoutCallableProcessingInterceptor();
      }
    }
    

    方式三:和方式二差不多,在Callable外包一层,给WebAsyncTask设置一个超时回调,即可实现超时处理

        @RequestMapping(value = "/email/webAsyncReq", method = GET)
        @ResponseBody
        public WebAsyncTask<String> webAsyncReq () {
            System.out.println("外部线程:" + Thread.currentThread().getName());
            Callable<String> result = () -> {
                System.out.println("内部线程开始:" + Thread.currentThread().getName());
                try {
                    TimeUnit.SECONDS.sleep(4);
                } catch (Exception e) {
                    // TODO: handle exception
                }
                logger.info("副线程返回");
                System.out.println("内部线程返回:" + Thread.currentThread().getName());
                return "success";
            };
            WebAsyncTask<String> wat = new WebAsyncTask<String>(3000L, result);
            wat.onTimeout(new Callable<String>() {
    
                @Override
                public String call() throws Exception {
                    // TODO Auto-generated method stub
                    return "超时";
                }
            });
            return wat;
        }
    

    方式四:DeferredResult可以处理一些相对复杂一些的业务逻辑,最主要还是可以在另一个线程里面进行业务处理及返回,即可在两个完全不相干的线程间的通信。

    @RequestMapping(value = "/email/deferredResultReq", method = GET)
        @ResponseBody
        public DeferredResult<String> deferredResultReq () {
            System.out.println("外部线程:" + Thread.currentThread().getName());
            //设置超时时间
            DeferredResult<String> result = new DeferredResult<String>(60*1000L);
            //处理超时事件 采用委托机制
            result.onTimeout(new Runnable() {
    
                @Override
                public void run() {
                    System.out.println("DeferredResult超时");
                    result.setResult("超时了!");
                }
            });
            result.onCompletion(new Runnable() {
    
                @Override
                public void run() {
                    //完成后
                    System.out.println("调用完成");
                }
            });
            myThreadPoolTaskExecutor.execute(new Runnable() {
    
                @Override
                public void run() {
                    //处理业务逻辑
                    System.out.println("内部线程:" + Thread.currentThread().getName());
                    //返回结果
                    result.setResult("DeferredResult!!");
                }
            });
           return result;
        }
    

    二、SpringBoot中异步调用的使用

    1、介绍

    异步请求的处理。除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。

    2、使用方式(基于spring下)

    需要在启动类加入@EnableAsync使异步调用@Async注解生效

    在需要异步执行的方法上加入此注解即可@Async("threadPool"),threadPool为自定义线程池

    代码略。。。就俩标签,自己试一把就可以了

    3、注意事项

    在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。所以最好我们来自定义一个线程池。

    调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。

    其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。下面会重点讲述。

    4、什么情况下会导致@Async异步方法会失效?

    a.调用同一个类下注有@Async异步方法:在spring中像@Async和@Transactional、cache等注解本质使用的是动态代理,其实Spring容器在初始化的时候Spring容器会将含有AOP注解的类对象“替换”为代理对象(简单这么理解),那么注解失效的原因就很明显了,就是因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器,那么解决方法也会沿着这个思路来解决。

    b.调用的是静态(static )方法

    c.调用(private)私有化方法

    5、解决4中问题1的方式(其它2,3两个问题自己注意下就可以了)

    将要异步执行的方法单独抽取成一个类,原理就是当你把执行异步的方法单独抽取成一个类的时候,这个类肯定是被Spring管理的,其他Spring组件需要调用的时候肯定会注入进去,这时候实际上注入进去的就是代理类了。

    其实我们的注入对象都是从Spring容器中给当前Spring组件进行成员变量的赋值,由于某些类使用了AOP注解,那么实际上在Spring容器中实际存在的是它的代理对象。那么我们就可以通过上下文获取自己的代理对象调用异步方法。

    @Controller
    @RequestMapping("/app")
    public class EmailController {
    
        //获取ApplicationContext对象方式有多种,这种最简单,其它的大家自行了解一下
        @Autowired
        private ApplicationContext applicationContext;
    
        @RequestMapping(value = "/email/asyncCall", method = GET)
        @ResponseBody
        public Map<String, Object> asyncCall () {
            Map<String, Object> resMap = new HashMap<String, Object>();
            try{
                //这样调用同类下的异步方法是不起作用的
                //this.testAsyncTask();
                //通过上下文获取自己的代理对象调用异步方法
                EmailController emailController = (EmailController)applicationContext.getBean(EmailController.class);
                emailController.testAsyncTask();
                resMap.put("code",200);
            }catch (Exception e) {
                resMap.put("code",400);
                logger.error("error!",e);
            }
            return resMap;
        }
    
        //注意一定是public,且是非static方法
        @Async
        public void testAsyncTask() throws InterruptedException {
            Thread.sleep(10000);
            System.out.println("异步任务执行完成!");
        }
    
    }
    

    6、开启cglib代理,手动获取Spring代理类,从而调用同类下的异步方法。

    首先,在启动类上加上@EnableAspectJAutoProxy(exposeProxy = true)注解。

    代码实现,如下:

    @Service
    @Transactional(value = "transactionManager", readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
    public class EmailService {
    
        @Autowired
        private ApplicationContext applicationContext;
    
        @Async
        public void testSyncTask() throws InterruptedException {
            Thread.sleep(10000);
            System.out.println("异步任务执行完成!");
        }
    
    
        public void asyncCallTwo() throws InterruptedException {
            //this.testSyncTask();
    //        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);
    //        emailService.testSyncTask();
            boolean isAop = AopUtils.isAopProxy(EmailController.class);//是否是代理对象;
            boolean isCglib = AopUtils.isCglibProxy(EmailController.class);  //是否是CGLIB方式的代理对象;
            boolean isJdk = AopUtils.isJdkDynamicProxy(EmailController.class);  //是否是JDK动态代理方式的代理对象;
            //以下才是重点!!!
            EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);
            EmailService proxy = (EmailService) AopContext.currentProxy();
            System.out.println(emailService == proxy ? true : false);
            proxy.testSyncTask();
            System.out.println("end!!!");
        }
    }
    展开全文
  • 在搜集资料时发现一篇文章《SpringBoot中异步请求和异步调用》,以及许多它的衍生文章。文章的确不错,但文章中提到的“异步请求和异步调用”这两个概念,一直百思不得其解,衍生文章中也是在混着用。 终于忍不住想...

    当你打开本文,首先想说的是,这篇文章不仅仅是在讲一个知识点,更重要的是在讲一种学习方法:质疑与求证。

    前言

    最近在研究Spring Boot的异步处理,准备写一篇文章。在搜集资料时发现一篇文章《SpringBoot中异步请求和异步调用》,以及许多它的衍生文章。文章的确不错,但文章中提到的“异步请求和异步调用”这两个概念,一直百思不得其解,衍生文章中也是在混着用。

    终于忍不住想探究一番,发现除了那篇文章和衍生的文章之外,并没有其他证据来区别这两个概念。直到在Stack Overflow上搜到了一篇七年前的文章,这个疑惑才解开。

    今天就带大家一起探究一下“异步请求和异步调用”这两个概念。

    异步请求和异步调用的区别

    上面提到的文章中有这样两段话来讲异步请求和异步调用的区别:

    区别一:异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算和响应的任务,比如同步日志到kafka中做日志分析等。

    区别二:异步请求是会一直等待response相应的,需要返回结果给客户端的;而异步调用往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台运行就可以了,客户端不会关心。

    仔细阅读和分析之后,文章中提到的异步请求应该指的是通过Servlet的AsyncContext对象或Spring中的Callable、DeferredResult、WebAsyncTask等方式进行业务的异步处理。而文中异步调用指的是类似在Spring中调用@Async注解的异步方法。

    你看了上面的两个概念的区别,是否觉得很有道理?感觉已经成功区分了异步请求和异步调用了吗?

    那么,是否想过一个问题,我们通常说请求一个接口和调用一个接口时难道不是一个意思吗?怎么在这里就变成两个截然不同的概念了?

    疑问探究

    带着上面的疑问,开始用搜索引擎进行搜索。发现当搜索“异步请求和异步调用的区别”时,搜出来的内容基本上都来自上述文章及衍生文章,或者是讲异步和同步的区别。这并不能说明什么,但也说明了这两个概念的区别并没有得到普遍的认可。

    于是开始尝试通过英文搜索引擎进行搜索。先搜了“the difference between asynchronous call and asynchronous request”,没有想要的结果,大多数文章还是讲异步和同步的区别。

    改变关键字搜“the difference between asynchronous call and asynchronous method”,结果一样,并没有想要的文章。但却搜出了一个Stack Overflow上的关联问题“Synchronous Vs Asynchronous related to web services”。

    在该问题的下面出现了两个词组:Asynchronous service和Asynchronous calls。再仔细看两个词组的解释,发现正是想要的结果。

    异步调用与异步服务

    其实在英文搜索的过程中已经发现了,探讨异步与同步的区别时,经常会出现asynchronous request和asynchronous call这两个词组,而且它们都是在讲异步与同步的区别,而没有讲它们两个的区别。所以无论异步调用还是异步请求本质上是一回事。

    而《SpringBoot中异步请求和异步调用》一文中所说的异步请求和异步调用的区别,概念上也并没有说清楚什么是异步请求,什么是异步调用。而且只有那几篇文章这么讲,所以可以认为异步请求和异步调用是同一个概念(asynchronous call)。而作者所讲的“异步调用”指的应该异步服务(Asynchronous service)。

    为了进一步证明上面的结论,下面将问答中对两个概念的解释简单翻译一下。

    Asynchronous service和Asynchronous calls

    Asynchronous service

    当你的web服务执行需要耗费大量时间,比如从文件系统中读取一个大文件。此时,如果你使用“同步”的web服务,那么客户端必须等待处理完成,通常会阻塞线程,对于高并发的服务,会出现性能问题。

    如果将web服务设置为异步服务,那么可以把耗时服务委托给另外一个线程,或者使用非阻塞机制,在未来的某刻进行返回。还以读取大文件为例,可以使用异步的ReadFile方法来读取大文件。这样便能避免线程阻塞,提高吞吐量。

    Asynchronous calls

    值得注意的是:你可以通过异步的方式调用一个非异步的的服务。比如在JavaScript中的Ajax的调用:

    var jqxhr = $.ajax( "AnyService.svc" )
        .done(function() { alert("success"); })
        .fail(function() { alert("error"); })
        .always(function() { alert("complete"); });
    
    alert("Called");
    

    执行上述示例,首先展示“Called”,然后展示“success”,因为执行的过程中并不需要等到服务返回的结果。而被调用的服务也不需要是异步的。

    总之,服务实现的异步与同步特性完全独立于客户端调用的异步和同步特性。也就是说客户端可以异步的去调用同步服务,而且客户端也可以同步的去调用异步服务。

    小结

    通过上面的追踪分析,关于异步请求和异步调用我们可以理解为是一回事,同时我们还得知了所谓的客户端异步和同步与服务的异步与同步是相互独立的。也就是客户端可以异步请求也可以同步请求,服务可以异步处理也可以同步处理。两两组合可以有四种情况。

    另外,更重要的是我们要学会通过搜索中英文资料来解答自己心中的疑惑,而且英文资料相对更准确一些,所以首推英文。在探索疑惑问题的过程中往往还能有不少意外的收获。

    原文链接:《异步请求和异步调用有区别?直到看到了7年前的一个问答


    程序新视界

    公众号“程序新视界”,一个让你软实力、硬技术同步提升的平台,提供海量资料

    微信公众号:程序新视界

    展开全文
  • 一、Spring Boot中异步请求的使用1、异步请求与同步请求特点:可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对...
    321b503654aa081cc69da86869bc8d88.png

    一、Spring Boot中异步请求的使用

    1、异步请求与同步请求

    1d2392849816a30ab7e90a75dc9f1427.png
    8a7a362adde2ab8c7e3bd037756b9a04.png

    特点:

    可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。

    一句话:增加了服务器对客户端请求的吞吐量(实际生产上我们用的比较少,如果并发请求量很大的情况下,我们会通过nginx把请求负载到集群服务的各个节点上来分摊请求压力,当然还可以通过消息队列来做请求的缓冲)

    2、异步请求的实现

    方式一:Servlet方式实现异步请求

    @RequestMapping(value= "/email/servletReq", method = GET)
    publicvoidservletReq(HttpServletRequest request, HttpServletResponse response) {
    AsyncContext asyncContext = request.startAsync();
    //设置监听器:可设置其开始、完成、异常、超时等事件的回调处理
    asyncContext.addListener(newAsyncListener() {
    @Override
    publicvoidonTimeout(AsyncEvent event) throws IOException 
    {
    System.out.println("超时了...");
    //做一些超时后的相关操作...
    }
    @Override
    publicvoidonStartAsync(AsyncEvent event) throws IOException 
    {
    System.out.println("线程开始");
    }
    @Override
    publicvoidonError(AsyncEvent event) throws IOException 
    {
    System.out.println("发生错误:"+event.getThrowable());
    }
    @Override
    publicvoidonComplete(AsyncEvent event) throws IOException 
    {
    System.out.println("执行完成");
    //这里可以做一些清理资源的操作...
    }
    });
    //设置超时时间
    asyncContext.setTimeout(20000);
    asyncContext.start(newRunnable() {
    @Override
    publicvoidrun() 
    {
    try{
    Thread.sleep(10000);
    System.out.println("内部线程:"+ Thread.currentThread().getName());
    asyncContext.getResponse().setCharacterEncoding("utf-8");
    asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
    asyncContext.getResponse().getWriter().println("这是异步的请求返回");
    } catch(Exception e) {
    System.out.println("异常:"+e);
    }
    //异步请求完成通知
    //此时整个请求才完成
    asyncContext.complete();
    }
    });
    //此时之类 request的线程连接已经释放了
    System.out.println("主线程:"+ Thread.currentThread().getName());
    }

    方式二:使用很简单,直接返回的参数包裹一层callable即可,可以继承WebMvcConfigurerAdapter类来设置默认线程池和超时处理。

    @RequestMapping(value = "/email/callableReq", method = GET)
    @ResponseBody
    publicCallable callableReq(){
    System.out.println("外部线程:"+ Thread.currentThread().getName());
    returnnewCallable() {
    @Override
    publicString call()throwsException {
    Thread.sleep(10000);
    System.out.println("内部线程:"+ Thread.currentThread().getName());
    return"callable!";
    }
    };
    }
    @Configuration
    publicclassRequestAsyncPoolConfigextendsWebMvcConfigurerAdapter{
    @Resource
    privateThreadPoolTaskExecutor myThreadPoolTaskExecutor;
    @Override
    publicvoidconfigureAsyncSupport(finalAsyncSupportConfigurer configurer){
    //处理 callable超时
    configurer.setDefaultTimeout(60*1000);
    configurer.setTaskExecutor(myThreadPoolTaskExecutor);
    configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());
    }
    @Bean
    publicTimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor(){
    returnnewTimeoutCallableProcessingInterceptor();
    }
    }

    方式三:和方式二差不多,在Callable外包一层,给WebAsyncTask设置一个超时回调,即可实现超时处理。

    @RequestMapping(value = "/email/webAsyncReq", method = GET)
    @ResponseBody
    publicWebAsyncTask webAsyncReq () {
    System.out.println("外部线程:"+ Thread.currentThread().getName());
    Callable result = () -> {
    System.out.println("内部线程开始:"+ Thread.currentThread().getName());
    try{
    TimeUnit.SECONDS.sleep(4);
    } catch(Exception e) {
    // TODO:handle exception
    }
    logger.info("副线程返回");
    System.out.println("内部线程返回:"+ Thread.currentThread().getName());
    return"success";
    };
    WebAsyncTask wat = newWebAsyncTask(3000L, result);
    wat.onTimeout(newCallable() {
    @Override
    publicStringcall() throws Exception {
    // TODO Auto-generated method stub
    return"超时";
    }
    });
    returnwat;
    }

    方式四:DeferredResult可以处理一些相对复杂一些的业务逻辑,最主要还是可以在另一个线程里面进行业务处理及返回,即可在两个完全不相干的线程间的通信。

    @RequestMapping(value= "/email/deferredResultReq", method = GET)
    @ResponseBody
    publicDeferredResult deferredResultReq() 
    {
    System.out.println("外部线程:"+ Thread.currentThread().getName());
    //设置超时时间
    DeferredResult result = newDeferredResult(60*1000L);
    //处理超时事件 采用委托机制
    result.onTimeout(newRunnable() {
    @Override
    publicvoidrun() 
    {
    System.out.println("DeferredResult超时");
    result.setResult("超时了!");
    }
    });
    result.onCompletion(newRunnable() {
    @Override
    publicvoidrun() 
    {
    //完成后
    System.out.println("调用完成");
    }
    });
    myThreadPoolTaskExecutor.execute(newRunnable() {
    @Override
    publicvoidrun() 
    {
    //处理业务逻辑
    System.out.println("内部线程:"+ Thread.currentThread().getName());
    //返回结果
    result.setResult("DeferredResult!!");
    }
    });
    returnresult;
    }

    二、Spring Boot中异步调用的使用

    1、介绍

    异步请求的处理。除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。

    2、使用方式(基于spring下)

    需要在启动类加入@EnableAsync使异步调用@Async注解生效

    在需要异步执行的方法上加入此注解即可@Async("threadPool"),threadPool为自定义线程池。

    代码略。。。就俩标签,自己试一把就可以了

    3、注意事项

    在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。所以最好我们来自定义一个线程池。

    调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。

    其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。下面会重点讲述。。

    4、什么情况下会导致@Async异步方法会失效?

    调用同一个类下注有@Async异步方法:

    在spring中像@Async和@Transactional、cache等注解本质使用的是动态代理,其实Spring容器在初始化的时候Spring容器会将含有AOP注解的类对象“替换”为代理对象(简单这么理解),那么注解失效的原因就很明显了,就是因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器,那么解决方法也会沿着这个思路来解决。

    调用的是静态(static )方法

    调用(private)私有化方法

    5、解决4中问题1的方式(其它2,3两个问题自己注意下就可以了)

    将要异步执行的方法单独抽取成一个类,原理就是当你把执行异步的方法单独抽取成一个类的时候,这个类肯定是被Spring管理的,其他Spring组件需要调用的时候肯定会注入进去,这时候实际上注入进去的就是代理类了。

    其实我们的注入对象都是从Spring容器中给当前Spring组件进行成员变量的赋值,由于某些类使用了AOP注解,那么实际上在Spring容器中实际存在的是它的代理对象。那么我们就可以通过上下文获取自己的代理对象调用异步方法。

    @Controller
    @RequestMapping("/app")
    publicclassEmailController {
    //获取ApplicationContext对象方式有多种,这种最简单,其它的大家自行了解一下
    @Autowired
    privateApplicationContext applicationContext;
    @RequestMapping(value = "/email/asyncCall", method = GET)
    @ResponseBody
    publicMap asyncCall () {
    Map resMap = newHashMap();
    try{
    //这样调用同类下的异步方法是不起作用的
    //this.testAsyncTask();
    //通过上下文获取自己的代理对象调用异步方法
    EmailController emailController = (EmailController)applicationContext.getBean(EmailController.class);
    emailController.testAsyncTask();
    resMap.put("code",200);
    }catch(Exception e) {
    resMap.put("code",400);
    logger.error("error!",e);
    }
    returnresMap;
    }
    //注意一定是public,且是非static方法
    @Async
    publicvoidtestAsyncTask() throws InterruptedException {
    Thread.sleep(10000);
    System.out.println("异步任务执行完成!");
    }
    }

    开启cglib代理,手动获取Spring代理类,从而调用同类下的异步方法。首先,在启动类上加上@EnableAspectJAutoProxy(exposeProxy = true)注解。代码实现,如下:

    @Service
    @Transactional(value = "transactionManager", readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
    publicclassEmailService{
    @Autowired
    privateApplicationContext applicationContext;
    @Async
    publicvoidtestSyncTask()throwsInterruptedException {
    Thread.sleep(10000);
    System.out.println("异步任务执行完成!");
    }
    publicvoidasyncCallTwo()throwsInterruptedException {
    //this.testSyncTask();
    // EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);
    // emailService.testSyncTask();
    booleanisAop = AopUtils.isAopProxy(EmailController.class);//是否是代理对象;
    booleanisCglib = AopUtils.isCglibProxy(EmailController.class); //是否是CGLIB方式的代理对象;
    booleanisJdk = AopUtils.isJdkDynamicProxy(EmailController.class); //是否是JDK动态代理方式的代理对象;
    //以下才是重点!!!
    EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);
    EmailService proxy = (EmailService) AopContext.currentProxy();
    System.out.println(emailService == proxy ? true: false);
    proxy.testSyncTask();
    System.out.println("end!!!");
    }
    }

    三、异步请求与异步调用的区别

    两者的使用场景不同,异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算和响应的任务,比如同步日志到kafka中做日志分析等。

    异步请求是会一直等待response相应的,需要返回结果给客户端的;而异步调用我们往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台自己慢慢跑就行,客户端不会关心。

    四、总结

    异步请求和异步调用的使用到这里基本就差不多了,有问题还希望大家多多指出。这边文章提到了动态代理,而spring中Aop的实现原理就是动态代理,后续会对动态代理做详细解读,还望多多支持哈。

    展开全文
  • 作者|会炼钢的小白龙 cnblogs.com/baixianlong/p/10661591.html一、Spring Boot中异步请求的使用1、异步请求与同步请求特点:可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求...
  • 一、Spring Boot中异步请求的使用1、异步请求与...5、解决4中问题1的方式(其它2,3两个问题自己注意下就可以了)三、异步请求异步调用区别四、总结《Java 2019 超神之路》《Dubbo 实现原理与源码解析 —— 精...
  • 之前一直用 gunicorn、Flask Celery 来实现前端请求异步调用,但是在大并发的情况下,存在一定问题。主要还是 gunicorn flask 的前端机制是 WSGI ,是一个prefork 的同步机制,导致并发性能不理想希望前端...
  • 同步调用、回调和异步调用区别

    千次阅读 2013-09-04 08:32:19
    在UNIX网络编程卷一中关于这两个概念的定义:  Posix.1定义这两个术语如下: ... 异步I/O操作不引起请求进程阻塞。 当然这也是其中的一种定义,关于同步异步定义还是有所不同,不仅仅是I/O方面。
  • 一、异步请求异步调用区别 两者的使用场景不同: 异步调用是用来做一些非主线流程且不需要实时计算响应的任务,比如同步日志到kafka中做日志分析,或保存历史数据等。 异步请求用来解决并发请求对服务器造成的...
  • 之前一直用 gunicorn、Flask Celery 来实现前端请求异步调用,但是在大并发的情况下,存在一定问题。主要还是 gunicorn flask 的前端机制是 WSGI ,是一个prefork 的同步机制,导致并发性能不理想希望前端...
  • SpringBoot异步调用方法

    千次阅读 2019-04-23 18:28:29
    一、spring boot--使用异步请求,提高系统的吞吐量 https://blog.csdn.net/liuchuanhong1/article/details/78744138 1.使用Callable来实现 2. 通过WebAsyncTask,也能实现异步调用 这种方式上面的callable方式...
  • 多线程的实现、CompletableFuture异步任务、@Async注解异步调用 一、异步多线程有什么区别? 其实,异步多线程并不时一个同等关系,异步是目的,多线程只是我们实现异步的一个手段. 什么是异步? 异步是当一个调用...
  • 同步和异步区别

    2016-01-25 16:37:30
    异步调用的概念同步相对,在一个异步调用发起后,被调用者立即返回给调用者,但调用者不能立刻得到结果,被调用者在实际处理这个调用的请求完成后,通过状态、通知或回调等方式来通知调用者请求处理的结果。...
  • **异步请求异步调用区别** - 两者的使用场景不同,**异步请求**用来解决`并发请求对服务器造成的压力`,从而`提高对请求的吞吐量`;而**异步调用**是用来`做一些非主线流程且不需要实时计算响应的任务`,比如...
  • 同步和异步区别?

    2020-04-16 10:01:28
    同步(Sync)发出一个功能调用时,必须一件一件事做,等前一件做完了才能做下一件事。异步(Async)当一个异步过程调用...总结来说,同步和异步区别请求发出后,是否需要等待结果,才能继续执行其他操作。 ...
  • 同步(Sync)发出一个功能调用时,必须一件一件事做,等前一件做完了才能做下一件事。异步(Async)当一个异步过程调用发出后...总结来说,同步和异步区别请求发出后,是否需要等待结果,才能继续执行其他操作。 ...
  • 一、同步和异步 同步和异步是一种 消息通知机制 同步:发出一次请求后必须等到该请求有返回结果,才能继续下一步工作;请求者需主动询问是否有返回结果; 异步:发出一次请求后无需等到该请求有返回结果,即可进行...
  • 同步IO和异步IO的区别

    2020-02-23 09:54:26
    同步IO和异步IO的区别 同步IO,是一种用户空间和内核空间的IO发起方式。同步是指用户空间的线程是主动发起IO请求的一方,内核空间是被动接受方。异步IO则反过来,是指系统内核是主动发起IO请求的一方,用户空间的...
  • 简述同步IO和异步IO的区别

    万次阅读 多人点赞 2018-02-01 11:25:00
    同步: 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。...异步的概念同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状
  • async await异步请求

    2020-08-19 16:03:40
    先理解同步和异步,异步和同步的区别就在于: 同步:会阻塞后续代码的执行 异步:不会阻塞代码执行 使用async await进行异步处理。 1.深入理解await与async async async作为一个关键字放在函数的前面,表示该函数...
  • 同步和异步,阻塞和非阻塞,这两组概念出现在IO操作中常常让人混淆不清。这里根据自己看到的,想到的,整理下自己的理解,权当交流。 IO操作其实可以分成两个步骤,请求IO操作和执行IO操作。一般的IO调用过程是这样...
  • 异步则是请求发送成功之后,这个调用直接返回,无需等待结果,被调用者通过状态来通知请求者,后回调函数处理这个调用。阻塞非阻塞 阻塞非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态 阻塞调用是...
  • 同步和异步区别:同步同时只能做一件事情,会阻塞代码的执行,异步可以同时进行多个操作,不会阻塞代码的执行 异步通过callback形式调用。 //异步 通过callback形式调用 异步不会阻塞代码执行 console.log(100) ...
  • 同步请求和异步请求区别在实现登录功能时已经介绍过了,下图便是实现异步请求分页查询的时序图。 可以看出我们首先进行用户列表的页面跳转,再进行用户的分页查询。 首先将同步请求方式的代码注释 添加id值用于...
  • 网络请求数据异步请求时,不会阻塞线程,在调用请求后,可以继续往下执行,而不用等请求有结果才能继续。 区别: 线程同步:是多个线程访问同一资源时,只有当前正在访问的线程访问结束之...
  • ICE学习(八)-异步方法调用AMI

    千次阅读 2015-10-23 17:04:10
    Asynchronous Method Invocation (AMI)...对于服务器端来说,同步和异步请求是没有什么区别的。 基本异步API 看下边的SLICE定义,使用slice2cpp生成.h,.cpp文件。 module Demo {   interface Employees {
  • .NET框架基类库中有好几种类都可以提供同步和异步的方法调用。 因为同步方法调用会导致程序流程中途等待,所以采用同步方法的情况下往往会导致程序执行的延迟 相比来说,在某些条件下选择异步方法调用就可能更好一些...
  • 那么阻塞和同步,非阻塞和异步是一回事儿嘛 ? 答案是当然不是。 重要的一点是针对的对象不一样,阻塞/非阻塞说的是调用者,同步/异步说的是被调用者。 举例子: A调用B ,B的处理是同步的,在处理完之前他不会通知A...

空空如也

空空如也

1 2 3 4 5 ... 13
收藏数 260
精华内容 104
关键字:

异步调用和异步请求区别