ITPub博客

首页 > 数据库 > 数据库开发技术 > 618来袭,大量优惠券生成太耗时?!ThreadPoolTaskExecutor线程池帮你来搞定!

618来袭,大量优惠券生成太耗时?!ThreadPoolTaskExecutor线程池帮你来搞定!

原创 数据库开发技术 作者:Finally_m 时间:2022-06-08 08:03:00 0 删除 编辑


点击上方蓝字关注我们

一. 问题阐述

最近耀哥一个已经参加工作的学生,要给他们公司迭代开发一个营销活动,其中有一个功能是:当运营人员录入一次活动的同时,需要给这次活动生成一定数量的礼品兑换券。

耀哥的这个学生,虽然把这个功能实现了,但效果却不尽理想,于是他就跑来咨询我该怎么进行优化。耀哥思索了一下,就结合他这个项目的实际场景,考虑到要生成大数量礼品券的过程比较耗时,于是耀哥就决定把生成礼品券的逻辑使用线程进行异步执行。

最终耀哥选择使用Spring下的线程池工具类ThreadPoolTask Executor来创建线程,经过这样一番技术优化后,耀哥就帮这个学生圆满地完成了他们公司布置的任务。

那么接下来耀哥就给大家介绍一下刚才提到的ThreadPoolTask Executor的使用方法和注意事项。

二. 快速使用

1. 创建项目入口类

首先我们创建一个入口类,并在该入口类上添加@EnableAsync注解。

@SpringBootApplication
@EnableAsync
public class AsyncdemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(AsyncdemoApplication.class, args);
    }
}   

2. 编写业务方法

接下来我们把需要异步执行的业务逻辑单独抽取成一个方法,在方法上也加上@Async注解。

@Slf4j
@Service
public class AsyncService {

    /**
     * 异步调用无返回值
     * @throws InterruptedException
     */

    @Async
    public void asyncProcess() throws InterruptedException {
        log.info("异步任务, 当前线程的名字是 -> {}",
                Thread.currentThread().getName());
    }

    /**
     * 带有返回值的异步调用
     * @throws InterruptedException
     */

    @Async
    public Future<Integer> asyncProcessHasReturn() throws InterruptedException {
        log.info("异步任务(有返回值), 当前线程的名字是  -> {}",
                Thread.currentThread().getName());
        TimeUnit.SECONDS.sleep(2);
        return new AsyncResult<>(100);
    }
}

3. 编写junit单元测试

接下来我们就编写一个单元测试类,对上面编写好的业务代码进行调用测试。

@SpringBootTest
@Slf4j
public class AsyncServiceTests {

    @Autowired
    private AsyncService asyncService;

    /**
     * 测试无返回值的线程调用
     * @throws InterruptedException
     */

    @Test
    public void test01() throws InterruptedException {
        log.info("调用开始,当前线程是 {}",Thread.currentThread().getName());
        asyncService.asyncProcess();
        Thread.sleep(100);
        log.info("调用结束,当前线程是 {}",Thread.currentThread().getName());
    }

    /**
     * 测试有返回值的线程调用
     * @throws InterruptedException
     */

    @Test
    public void test02() throws InterruptedException, ExecutionException {
        log.info("调用开始,当前线程是  {}",Thread.currentThread().getName());
        Future<Integer> integerFuture = asyncService.asyncProcessHasReturn();
        Integer integer = integerFuture.get();
        System.out.println(integer);
        log.info("调用结束,当前线程是 {}",Thread.currentThread().getName());
    }
}

我们可以分别执行两个测试用例。

无返回值的测试用例的测试结果:

有返回值的测试用例的测试结果:

三. 程序优化

从上面的测试结果中我们不难看出,在Spring下我们要使用线程池其实非常的简单,只需在要异步调用的方法上加上@Async注解即可。以后调用该方法时,Spring就会自动使用默认的线程池,创建一个线程异步调用该方法。

但这样做会不会有什么问题呢?有没有我们需要优化的点呢?

答案是肯定的!Spring默认使用的Executor bean,和我们使用JDK中的Executors创建的线程池一样,都存在着阻塞队列的长度过长,可能会堆积大量的请求,从而导致OOM的问题。所以为了规避资源耗尽,我们一般会自己配置Executor bean(具体描述参照阿里巴巴开发规范1.4版),具体优化代码如下:

@Configuration
@Slf4j
public class AsyncConfig implements AsyncConfigurer {

    @Override
    @Bean("cs2105ThreadPool")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        // 设置核心线程数
        executor.setCorePoolSize(10);
        // 设置最大线程数
        executor.setMaxPoolSize(20);
        // 设置阻塞队列的容量
        executor.setQueueCapacity(20);
        // 除了核心线程数以外的线程的存货时间
        executor.setKeepAliveSeconds(60);
        // 设置线程池的线程的名称的前缀
        executor.setThreadNamePrefix("cs2105_");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        // 拒绝策略
        executor.setRejectedExecutionHandler(
                new ThreadPoolExecutor.AbortPolicy()
        );
        executor.initialize();
        return executor;
    }

配置了自定义的Executor bean 之后,我们再次运行测试case,发现运行结果如下:

四. 结语

线程池在Java中一直都是一个非常重要的知识点,我们在面试中也会经常遇到这方面的题,希望大家好好掌握这里面的基础知识。最后耀哥给大家分享一个可以随时随地免费学习的良心小程序,大家可以扫描下图中的二维码,领取海量免费学习资料。

本月福利,关注公众号回复【福利】抢书咯!

今日份干货,学会的同学回复“666”!

精选第一名,可以领取办公鼠标垫一个哦!

点击【阅读原文】,零基础免费学Java!

原文作者:Java架构栈;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2021-04-13

  • 博文量
    573
  • 访问量
    243893