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

一. 问题阐述

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

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

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

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

二. 快速使用

1. 创建项目入口类

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

@SpringBootApplication
@EnableAsync
public class AsyncdemoApplication {

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

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值