• Spring @Async异步方法中的线程隔离


    Spring @Async异步方法中的线程隔离

    转载说明

    https://www.cnblogs.com/didispace/p/15307355.html

    内容摘要

    上一篇分享中介绍了Spring中的异步方法的使用方法,文中提到,如果我们不显示创建线程池,异步方法会放到Spring默认提供的线程池SimpleAsyncTaskExecutor中执行, 但是需要注意的是,Spring提供的这个默认线程池的队列长度是没有限制的,也就是说,如果一直往默认线程池你提交任务有可能OOM的风险,所以一般情况都建议使用自己创建的线程池。

    本文提到的线程隔离,则是定义多个线程池,它的使用场景是,如果是完全两类独立的业务,业务A比较耗时,另外一个业务B处理速度很快,如果使用同一个线程池,就会拉低业务B的任务处理效率。所以如果需要解决这个问题就需要让异步方法指派到不同的线程池,以便让不同的异步业务互相不影响。

    代码实例

    第一步:创建线程池

    @EnableAsync
    @Configuration
    public class ThreadPoolConfig {
    
        @Bean
        public Executor taskExecutor1(){
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(2);
            executor.setMaxPoolSize(2);
            executor.setQueueCapacity(10);
            executor.setKeepAliveSeconds(60);
            //用executor.setThreadNamePrefix设置了线程名的前缀
            executor.setThreadNamePrefix("executor-1-");
            // 如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            return executor;
        }
    
        @Bean
        public Executor taskExecutor2(){
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(2);
            executor.setMaxPoolSize(2);
            executor.setQueueCapacity(10);
            executor.setKeepAliveSeconds(60);
            executor.setThreadNamePrefix("executor-2-");
            // 如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            return executor;
        }
    }
    

    第二步:编写异步方法

    这里@Async注解中定义的taskExecutor1taskExecutor2就是线程池的名字。由于在第一步中,我们没有具体写两个线程池Bean的名称,所以默认会使用方法名,也就是taskExecutor1taskExecutor2

    @Service
    @Slf4j
    public class AsyncService {
        public static Random random = new Random();
    
        @Async("taskExecutor1")
        public CompletableFuture<String> doTaskOne(String taskNo) throws Exception{
            log.info("开始任务:{}", taskNo);
            long start = System.currentTimeMillis();
            Thread.sleep(random.nextInt(10000));
            long end = System.currentTimeMillis();
            log.info("完成任务:{},耗时:{} 毫秒", taskNo, end - start);
            return CompletableFuture.completedFuture("任务完成");
        }
    
        @Async("taskExecutor2")
        public CompletableFuture<String> doTaskTwo(String taskNo) throws Exception {
            log.info("开始任务:{}", taskNo);
            long start = System.currentTimeMillis();
            Thread.sleep(random.nextInt(10000));
            long end = System.currentTimeMillis();
            log.info("完成任务:{},耗时:{} 毫秒", taskNo, end - start);
            return CompletableFuture.completedFuture("任务完成");
        }
    }
    

    第三步:测试观察

    在上面的单元测试中,一共启动了6个异步任务,前三个用的是线程池1,后三个用的是线程池2。

    @SpringBootTest
    class AsyncDemoApplicationTests {
    
        @Test
        void contextLoads() {
        }
    
        @Autowired
        AsyncService service;
        @Test
        public void test01() throws Exception{
            // 线程池1
            CompletableFuture<String> task1 = service.doTaskOne("1");
            CompletableFuture<String> task2 = service.doTaskOne("2");
            CompletableFuture<String> task3 = service.doTaskOne("3");
    
            // 线程池2
            CompletableFuture<String> task4 = service.doTaskTwo("4");
            CompletableFuture<String> task5 = service.doTaskTwo("5");
            CompletableFuture<String> task6 = service.doTaskTwo("6");
    
            // 一起执行
            CompletableFuture.allOf(task1, task2, task3, task4, task5, task6).join();
        }
    }
    
    INFO 5524 --- [   executor-1-1] c.l.a.asyncdemo.service.AsyncService     : 开始任务:1
    INFO 5524 --- [   executor-2-2] c.l.a.asyncdemo.service.AsyncService     : 开始任务:5
    INFO 5524 --- [   executor-1-2] c.l.a.asyncdemo.service.AsyncService     : 开始任务:2
    INFO 5524 --- [   executor-2-1] c.l.a.asyncdemo.service.AsyncService     : 开始任务:4
    INFO 5524 --- [   executor-2-1] c.l.a.asyncdemo.service.AsyncService     : 完成任务:4,耗时:614 毫秒
    INFO 5524 --- [   executor-2-1] c.l.a.asyncdemo.service.AsyncService     : 开始任务:6
    INFO 5524 --- [   executor-2-2] c.l.a.asyncdemo.service.AsyncService     : 完成任务:5,耗时:3188 毫秒
    INFO 5524 --- [   executor-1-2] c.l.a.asyncdemo.service.AsyncService     : 完成任务:2,耗时:5314 毫秒
    INFO 5524 --- [   executor-1-2] c.l.a.asyncdemo.service.AsyncService     : 开始任务:3
    INFO 5524 --- [   executor-2-1] c.l.a.asyncdemo.service.AsyncService     : 完成任务:6,耗时:5539 毫秒
    INFO 5524 --- [   executor-1-1] c.l.a.asyncdemo.service.AsyncService     : 完成任务:1,耗时:6622 毫秒
    INFO 5524 --- [   executor-1-2] c.l.a.asyncdemo.service.AsyncService     : 完成任务:3,耗时:6136 毫秒
    
    边系鞋带边思考人生.
  • 相关阅读:
    默认组件加载类
    加密工具类
    客户端安全传输密码至服务端的实现改进
    开发Angular库的简单指导(译)
    Windows中搭建Redis集群
    Mybatis中SqlMapper配置的扩展与应用(3)
    Mybatis中SqlMapper配置的扩展与应用(2)
    Mybatis中SqlMapper配置的扩展与应用(1)
    优化与扩展Mybatis的SqlMapper解析
    使用XSD校验Mybatis的SqlMapper配置文件(2)
  • 原文地址:https://www.cnblogs.com/crazytrip/p/15328763.html
Copyright © 2020-2023  润新知