一. 问题阐述
最近千锋java老师一个已经参加工作的学生,要给他们公司迭代开发一个营销活动,其中有一个功能是:当运营人员录入一次活动的同时,需要给这次活动生成一定数量的礼品兑换券。
千锋java老师的这个学生,虽然把这个功能实现了,但效果却不尽理想,于是他就跑来咨询我该怎么进行优化。千锋java老师思索了一下,就结合他这个项目的实际场景,考虑到要生成大数量礼品券的过程比较耗时,于是千锋java老师就决定把生成礼品券的逻辑使用线程进行异步执行。
最终千锋java老师选择使用Spring下的线程池工具类ThreadPoolTaskExecutor来创建线程,经过这样一番技术优化后,千锋java老师就帮这个学生完满完成了他们公司布置的任务。那么接下来千锋java老师就给大家介绍一下刚才提到的ThreadPoolTaskExecutor的使用方法和注意事项。
二. 快速使用
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 FutureasyncProcessHasReturn() 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());
FutureintegerFuture = 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,发现运行结果如下:
image.png
四. 结语
线程池在Java中一直都是一个非常重要的知识点,我们在面试中也会经常遇到这方面的题,希望大家好好掌握这里面的基础知识。
相关文章
关注千锋学习站小程序
随时随地免费学习课程
扫一扫快速进入
千锋移动端页面
扫码匿名提建议
直达CEO信箱