• SpringBoot异步调用


    一、Spring框架为我们提供了基于线程池的异步调用支持,用法也很简单。

    特别注意:通常调用方法写在contorller类中,而异步执行业务逻辑放在service类中

    1.controller方法本身就在servlet容器的线程池中同步执行。
    2.若controller方法被标记为异步执行,则这个方法会被提交到非servlet容器线程池。
    3.若controller方法为同步执行,而被调用方法又需要异步执行,则被调用方法必须放在另一个类中(如service类),异步方法才会被提交到非servlet容器线程池,否则,被调用方法仍然在servlet容器线程池中与controller方法一起同步执行,违背目的。

    更正补充:异步失效的原因,由于调用方法和被调用方法在同一个对象所导致,具体细节可搜索【@Transactional和@Async注解失效】,由于spring中的事务注解和异步注解提供的增强逻辑都是基于动态代理实现的,要想让增强逻辑生效,被调用方法必须来自代理对象。
    从代理对象这个点出发,解决上述注解失效的方法:
    1. 将被调用方法单独放在一个类里,由spring容器构造代理bean,然后注入使用。
    2. 不单独出类,在本类中依赖注入本类的bean(这个bean由spring容器托管,实为代理类的bean),作为成员变量,被调用方法由这个成员变量的bean提供。
    3. 显式使用cglib,从AopContext.currentProxy()获取代理对象。
    

    二、使用默认线程池

    如果用户未声明异步线程池,spring将开启一个默认线程池来处理异步调用。

    1. 用@EnableAsync注解开启应用的异步调用功能。
    @EnableAsync
    @Configuration
    public class AsyncConfig {
    }
    
    1. 在controller类中给需要异步执行的方法上打上@Async注解,告诉spring此方法需要异步执行(此方法必须可被Overied)。
    /**
     * @author: Yang
     * @date: 2020/4/6 15:09
     * @description:
     */
    @Slf4j
    @RestController
    @RequestMapping("/async")
    public class AsyncController {
    
        @Resource
        private AsyncService asyncService;
    
        /**
         * 无调用关系,无返回值的异步方法
         */
        @Async
        @GetMapping("/sameClass")
        public void sameClass() {
            log.error(Thread.currentThread().getName());
        }
    
        /**
         * 有调用关系,无返回值的异步调用
         */
        @GetMapping("/noResult")
        public void noResult() {
            this.asyncService.no();
        }
    
        /**
         * 有调用关系,有返回值的异步调用
         *
         * @return
         */
        @GetMapping("/haveResult")
        public Long haveResult() {
            Long kk = null;
            Future<Long> have = this.asyncService.have();
            if (have.isDone()) {
                try {
                    kk = have.get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } finally {
                    Thread.interrupted();
                }
            }
            return kk;
        }
    }
    
    1. 在service类中给需要异步执行的方法上打上@Async注解,告诉spring此方法需要异步执行(此方法必须可被Overied)。
    @Slf4j
    @Component
    class AsyncService {
        @Async
        protected void no() {
            log.error(Thread.currentThread().getName());
        }
    
        @Async
        protected Future<Long> have() {
            log.error(Thread.currentThread().getName());
            log.error("---------{}", Thread.currentThread().getId());
            return new AsyncResult<>(Thread.currentThread().getId());
        }
    }
    
    1. 分别在浏览器访问几次下列接口进行测试:
    http://localhost:8081/async/sameClass //控制台打印出线程name【task-1、task-2、task-3】
    http://localhost:8081/async/noResult  //控制台打印出线程name【task-1、task-2、task-3】
    http://localhost:8081/async/haveResult  //能够得到池中线程id【45,12,44】
    注:servlet容器线程池的线程名类似【http-nio-8081-exec-1】
    

    三、使用自定义线程池

    1. 默认线程池的方式使用的是容器的线程池,不能灵活的控制和业务隔离,建议实践中自定义线程池来处理。
    2. 只需另行申明一个自定义线程池,配置类实现AsyncConfigurer接口,异步执行的方法声明无区别。
    /**
     * @author: Yang
     * @date: 2020/4/6 14:04
     * @description:
     */
    @EnableAsync
    @Configuration
    public class AsyncConfig implements AsyncConfigurer {
    
        private static final int MAX_SIZE = 16;
        private static final int QUEUE_SIZE = 16;
        private static final int KEEP_ALIVE_SECOND = 600;
        private static final int AWAIT_TERMINAL_SECOND = 60;
        private static final int CORE_SIZE = Runtime.getRuntime().availableProcessors() * 2;
    
        @Bean
        @Override
        public Executor getAsyncExecutor() {
            ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
            pool.setMaxPoolSize(MAX_SIZE);
            pool.setCorePoolSize(CORE_SIZE);
            pool.setQueueCapacity(QUEUE_SIZE);
            pool.setKeepAliveSeconds(KEEP_ALIVE_SECOND);
            pool.setAllowCoreThreadTimeOut(false);
            /*
             *线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,
             *表示当线程池没有处理能力的时候,该策略会直接在execute方法的调用线程中运行这个任务;
             *如果执行程序已关闭,则会丢弃该任务
             */
            pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            pool.setThreadNamePrefix("async-");
            // 该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
            pool.setWaitForTasksToCompleteOnShutdown(true);
            pool.setAwaitTerminationSeconds(AWAIT_TERMINAL_SECOND);
            //初始化线程池
            pool.initialize();
            return pool;
        }
    
        @Override
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
            return null;
        }
    }
    
    1. 分别在浏览器访问几次下列接口进行测试:
    http://localhost:8081/async/sameClass //控制台打印出线程name【async----1、async----2、async----3】
    http://localhost:8081/async/noResult  //控制台打印出线程name【async----1、async----2、async----3】
    http://localhost:8081/async/haveResult  //能够得到池中线程id【45,12,44】
    

    四、多个异步线程池

    1. 有时候一个应用中需要异步调用的业务模块不止一个,建议各业务模块的异步线程池分开配置,异步执行方法分开提交,方便管理和错误排查。
    2. 开启和配置方式都差不多,只是需要在各个自定义线程池上声明池的名称,异步提交执行的方法上也带上对应的池名称。
    @Override
    @Bean("myAsyncPool")
    public Executor getAsyncExecutor() {
        //自定义线程池声明,略
    }
    
    @Async("myAsyncPool")
    protected void no() {
      //异步执行方法声明,略
    }
    
  • 相关阅读:
    bash中一次性给多个变量赋值命名管道的使用
    Mysql复制还原后root帐号权限丢失问题
    TC中HTB的使用备注
    Python 调用JS文件中的函数
    PIL图像处理模块,功能强大、简单易用(转)
    openfeign 实现动态Url
    Extjs 进度条的应用【转】
    Javascript 对象与数组中的函数下【转】
    Linq学习笔记之一:Linq To XML
    Sql Server查询语句的一些小技巧
  • 原文地址:https://www.cnblogs.com/JaxYoun/p/12643884.html
Copyright © 2020-2023  润新知