系列文章推荐阅读顺序:
对于异步执行的控制是我们保障自身应用健康的基本技能。本文通过自定义线程池的方式来控制异步调用的并发。
一、定义线程池
第一步,先在Spring Boot主类中定义一个线程池,比如:
package com.erbadagang.springboot.async;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@SpringBootApplication
@EnableAsync
public class AsyncApplication {
public static void main(String[] args) {
SpringApplication.run(AsyncApplication.class, args);
}
@Configuration
@EnableAsync
class TaskPoolConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("guoxiuzhiTE-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
}
上面我们通过使用ThreadPoolTaskExecutor创建了一个线程池,同时设置了以下这些参数:
- 核心线程数10:线程池创建时候初始化的线程数
- 最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
- 缓冲队列200:用来缓冲执行任务的队列
- 允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
- 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
- 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
二、使用线程池
在定义了线程池之后,我们如何让异步调用的执行任务使用这个线程池中的资源来运行呢?方法非常简单,我们只需要在@Async
注解中指定线程池名即可,比如:
package com.erbadagang.springboot.async.task;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.Random;
/**
* @description 通过线程池异步调用
* @ClassName: AsyncTask
* @author: 郭秀志 jbcode@126.com
* @date: 2020/7/24 11:08
* @Copyright:
*/
@Component
@Slf4j
public class AsyncTaskWithThreadPool {
public static Random random = new Random();
@Async("taskExecutor")
public void doTaskOne() throws Exception {
log.info("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(5000));
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(5000));
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(5000));
long end = System.currentTimeMillis();
log.info("完成任务三,耗时:" + (end - start) + "毫秒");
}
}
为了日志输出线程名,使用了@Slf4j
,需要引入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
我们来写个单元测试来验证一下
@Autowired
private AsyncTaskWithThreadPool asyncTaskWithThreadPool;
@Test
public void test() throws Exception {
asyncTaskWithThreadPool.doTaskOne();
asyncTaskWithThreadPool.doTaskTwo();
asyncTaskWithThreadPool.doTaskThree();
Thread.currentThread().join();
}
执行上面的单元测试,我们可以在控制台中看到所有输出的线程名前都是之前我们定义的线程池前缀名开始的,说明我们使用线程池来执行异步任务的试验成功了!
2020-07-24 11:29:08.783 INFO 11588 --- [ guoxiuzhiTE-1] c.e.s.a.task.AsyncTaskWithThreadPool : 开始做任务一
2020-07-24 11:29:08.783 INFO 11588 --- [ guoxiuzhiTE-3] c.e.s.a.task.AsyncTaskWithThreadPool : 开始做任务三
2020-07-24 11:29:08.783 INFO 11588 --- [ guoxiuzhiTE-2] c.e.s.a.task.AsyncTaskWithThreadPool : 开始做任务二
底线
本文源代码使用 Apache License 2.0开源许可协议,可从如下Gitee地址免费获取代码通过git clone
命令下载到本地或者直接点击链接通过浏览器方式查看源代码。