在上篇的Spring Boot文章中,已经通过《Spring Boot中使用@Async实现异步调用》一文介绍过如何使用@Async注解来实现异步调用了。但是,对于这些异步执行的控制是我们保障自身应用健康的基本技能。
本文我们就来学习一下,如果通过自定义线程池的方式来控制异步调用的并发。
本文中的例子我们可以在之前的例子基础上修改,也可以创建一个全新的Spring Boot项目来尝试。
一、定义线程池
第一步,先在Spring Boot主类中定义一个线程池,比如:
@Configuration
@EnableAsync
public class TaskConfig {
@Bean("taskExecutor")
public Executor taskExecutor(){
ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor();
poolExecutor.setCorePoolSize(10);
poolExecutor.setMaxPoolSize(20);
poolExecutor.setKeepAliveSeconds(60);
poolExecutor.setQueueCapacity(200);
poolExecutor.setThreadNamePrefix("zerahTask-");
poolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return poolExecutor;
}
}
上面我们通过使用ThreadPoolTaskExecutor创建了一个线程池,同时设置了以下这些参数:
- 核心线程数10:线程池创建时候初始化的线程数
- 最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
- 缓冲队列200:用来缓冲执行任务的队列
- 允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
- 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
- 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
二、使用线程池
在定义了线程池之后,我们如何让异步调用的执行任务使用这个线程池中的资源来运行呢?方法非常简单,我们只需要在 @Async注解中指定线程池名即可,比如:
@Slf4j
@Component
public class Task {
public static Random random = new Random();
@Async("taskExecutor")
public void doTaskOne() throws Exception {
log.info("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
log.info("完成任务一,耗时:" + (end - start) + "毫秒");
}
@Async("taskExecutor")
public void doTaskTwo() throws Exception {
log.info("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
log.info("完成任务二,耗时:" + (end - start) + "毫秒");
}
@Async("taskExecutor")
public void doTaskThree() throws Exception {
log.info("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
log.info("完成任务三,耗时:" + (end - start) + "毫秒");
}
}
三、单元测试
最后,我们来写个单元测试来验证一下
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
private Task task;
@Test
public void test() throws Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
Thread.currentThread().join();
}
}
执行上面的单元测试,我们可以在控制台中看到所有输出的线程名前都是之前我们定义的线程池前缀名开始的,说明我们使用线程池来执行异步任务的试验成功了!
2019-09-14 16:21:00.366 INFO 10724 --- [ zerahTask-3] com.zerah.async.async.ThreadAsyncTask : 开始做任务三
2019-09-14 16:21:00.366 INFO 10724 --- [ zerahTask-1] com.zerah.async.async.ThreadAsyncTask : 开始做任务一
2019-09-14 16:21:00.366 INFO 10724 --- [ zerahTask-2] com.zerah.async.async.ThreadAsyncTask : 开始做任务二
2019-09-14 16:21:01.037 INFO 10724 --- [ zerahTask-1] com.zerah.async.async.ThreadAsyncTask : 完成任务一,耗时:671毫秒
2019-09-14 16:21:05.133 INFO 10724 --- [ zerahTask-2] com.zerah.async.async.ThreadAsyncTask : 完成任务二,耗时:4767毫秒
2019-09-14 16:21:08.832 INFO 10724 --- [ zerahTask-3] com.zerah.async.async.ThreadAsyncTask : 完成任务三,耗时:8466毫秒
四、线程池的拒绝策略
public void setRejectedExecutionHandler(@Nullable RejectedExecutionHandler rejectedExecutionHandler) {
this.rejectedExecutionHandler = (RejectedExecutionHandler)(rejectedExecutionHandler != null ? rejectedExecutionHandler : new AbortPolicy());
}
这里的RejectedExecutionHandler是拒绝策略的上层接口:
- RejectedExecutionHandler
- DiscardOldestPolicy 丢弃队列中最老的任务
- AbortPolicy 抛异常
- CallerRunsPolicy 将任务分给调用线程来执行
- DiscardPolicy 直接丢弃
同时,上面这四个也是ThreadPoolExecutor 的静态内部类。
线程池拒绝策略:
http://www.voidcn.com/article/p-berrqtbx-brq.html
https://juejin.im/post/5d08f6cf518825684b589422
https://blog.youkuaiyun.com/u010412719/article/details/52132613
crossoverJie :https://juejin.im/post/5b5e5fa96fb9a04fb900e1ce