频繁的创建、销毁线程和线程池,会给系统带来额外的开销。未经池化及统一管理的线程,则会导致系统内线程数上限不可控。随着访问数增加,系统内线程数持续增长,CPU负载逐步提高。极端情况下,甚至可能会导致CPU资源被吃满,整个服务不可用。
封装线程池
新增application.yml配置
这里主要是配置ThreadPoolTastExecutor比较重要的参数
# 定义一个线程池配置
thread:
# 使用ThreadPoolExecutor作为线程池实现
poolexecutor:
core-pool-size: 10 # 线程池的基本大小,核心线程一直存活
max-pool-size: 20 # 线程池最大大小
queue-capacity: 200 # 线程池任务队列的容量,任务将队列塞满之后,扩展核心线程,线程总数最多不超过最大线程数
keep-alive-seconds: 60 # 线程的空闲时间
thread-name-prefix: thread-pool-executor- # 线程名称的前缀
properties类
这个是为了简化代码,不用@Value一个一个去获取值
@Component
@ConfigurationProperties(prefix = "thread.poolexecutor")
public class ThreadPoolProperties {
private Integer corePoolSize;
private Integer maxPoolSize;
private Integer queueCapacity;
private Integer keepAliveSeconds;
private String threadNamePrefix;
// getter,setter
}
配置类
配置线程池,并将其注入到bean中
// 开启异步支持的配置类
@EnableAsync
// 配置类
@Configuration
public class ThreadPoolConfig {
// 自动注入线程池属性配置
@Autowired
private ThreadPoolProperties threadPoolProperties;
/**
* 创建并配置一个线程池任务执行器。
*
* @return Executor 返回配置好的线程池任务执行器实例。
*/
@Bean(name = "taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 根据配置设定线程池的核心大小,核心线程一直存活
executor.setCorePoolSize(threadPoolProperties.getCorePoolSize());
// 根据配置设定线程池的最大大小
executor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize());
// 根据配置设定线程池队列的容量,任务将队列塞满之后,扩展核心线程,线程总数最多不超过最大线程数
executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
// 根据配置设定线程的保持活跃时间
executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
// 设置线程名称前缀
executor.setThreadNamePrefix(threadPoolProperties.getThreadNamePrefix());
// 线程池关闭时等待所有任务完成再销毁
executor.setWaitForTasksToCompleteOnShutdown(true);
// 设置拒绝执行处理器为CallerRunsPolicy,即调用者运行策略:任务超出线程池容量后,新任务交还主线程处理
// 当提交的线程填满核心线程数,并且塞满了队列缓冲区,并且超过了最大线程数时就会触发拒绝策略;
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
封装Service和service实现类,方便管理
public interface ICustomAsync {
void doAsync();
}
@Service // 服务组件
public class CustomAsyncImpl implements ICustomAsync {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CustomAsyncImpl.class); // 初始化日志记录器
@Override
@Async("taskExecutor") // 指定使用名为"taskExecutor"的线程池进行异步执行
public void doAsync() {
logger.info("=== async start ===");
logger.info("线程: {}执行代码逻辑", Thread.currentThread().getName());
logger.info("=== async end ===");
}
}
测试
@SpringBootTest
public class CustomAsyncTest {
@Autowired
private ICustomAsync customAsync;
@Test
void doAsyncTests() {
for (int i = 0; i < 10; i++) {
customAsync.doAsync();
}
}
}
返回结果:
2024-03-28 10:49:31,326 INFO [thread-pool-executor-1] c.k.b.s.i.CustomAsyncImpl.(): === async start ===
2024-03-28 10:49:31,327 INFO [thread-pool-executor-3] c.k.b.s.i.CustomAsyncImpl.(): === async start ===
2024-03-28 10:49:31,328 INFO [thread-pool-executor-2] c.k.b.s.i.CustomAsyncImpl.(): === async start ===
2024-03-28 10:49:31,330 INFO [thread-pool-executor-5] c.k.b.s.i.CustomAsyncImpl.(): === async start ===
2024-03-28 10:49:31,332 INFO [thread-pool-executor-5] c.k.b.s.i.CustomAsyncImpl.(): 线程:thread-pool-executor-5执行代码逻辑
2024-03-28 10:49:31,332 INFO [thread-pool-executor-5] c.k.b.s.i.CustomAsyncImpl.(): === async end ===
2024-03-28 10:49:31,331 INFO [thread-pool-executor-2] c.k.b.s.i.CustomAsyncImpl.(): 线程:thread-pool-executor-2执行代码逻辑
2024-03-28 10:49:31,332 INFO [thread-pool-executor-2] c.k.b.s.i.CustomAsyncImpl.(): === async end ===
2024-03-28 10:49:31,332 INFO [thread-pool-executor-3] c.k.b.s.i.CustomAsyncImpl.(): 线程:thread-pool-executor-3执行代码逻辑
2024-03-28 10:49:31,333 INFO [thread-pool-executor-3] c.k.b.s.i.CustomAsyncImpl.(): === async end ===
2024-03-28 10:49:31,344 INFO [thread-pool-executor-4] c.k.b.s.i.CustomAsyncImpl.(): === async start ===
2024-03-28 10:49:31,346 INFO [thread-pool-executor-4] c.k.b.s.i.CustomAsyncImpl.(): 线程:thread-pool-executor-4执行代码逻辑
2024-03-28 10:49:31,350 INFO [thread-pool-executor-4] c.k.b.s.i.CustomAsyncImpl.(): === async end ===
2024-03-28 10:49:31,351 INFO [thread-pool-executor-1] c.k.b.s.i.CustomAsyncImpl.(): 线程:thread-pool-executor-1执行代码逻辑
2024-03-28 10:49:31,354 INFO [thread-pool-executor-1] c.k.b.s.i.CustomAsyncImpl.(): === async end ===
2024-03-28 10:49:31,357 INFO [thread-pool-executor-6] c.k.b.s.i.CustomAsyncImpl.(): === async start ===
2024-03-28 10:49:31,358 INFO [thread-pool-executor-6] c.k.b.s.i.CustomAsyncImpl.(): 线程:thread-pool-executor-6执行代码逻辑
2024-03-28 10:49:31,359 INFO [thread-pool-executor-6] c.k.b.s.i.CustomAsyncImpl.(): === async end ===
2024-03-28 10:49:31,362 INFO [thread-pool-executor-7] c.k.b.s.i.CustomAsyncImpl.(): === async start ===
2024-03-28 10:49:31,362 INFO [thread-pool-executor-8] c.k.b.s.i.CustomAsyncImpl.(): === async start ===
2024-03-28 10:49:31,362 INFO [thread-pool-executor-7] c.k.b.s.i.CustomAsyncImpl.(): 线程:thread-pool-executor-7执行代码逻辑
2024-03-28 10:49:31,362 INFO [thread-pool-executor-8] c.k.b.s.i.CustomAsyncImpl.(): 线程:thread-pool-executor-8执行代码逻辑
2024-03-28 10:49:31,362 INFO [thread-pool-executor-7] c.k.b.s.i.CustomAsyncImpl.(): === async end ===
2024-03-28 10:49:31,362 INFO [thread-pool-executor-8] c.k.b.s.i.CustomAsyncImpl.(): === async end ===
2024-03-28 10:49:31,364 INFO [thread-pool-executor-9] c.k.b.s.i.CustomAsyncImpl.(): === async start ===
2024-03-28 10:49:31,364 INFO [thread-pool-executor-9] c.k.b.s.i.CustomAsyncImpl.(): 线程:thread-pool-executor-9执行代码逻辑
2024-03-28 10:49:31,364 INFO [thread-pool-executor-9] c.k.b.s.i.CustomAsyncImpl.(): === async end ===
2024-03-28 10:49:31,367 INFO [thread-pool-executor-10] c.k.b.s.i.CustomAsyncImpl.(): === async start ===
2024-03-28 10:49:31,367 INFO [thread-pool-executor-10] c.k.b.s.i.CustomAsyncImpl.(): 线程:thread-pool-executor-10执行代码逻辑
2024-03-28 10:49:31,367 INFO [thread-pool-executor-10] c.k.b.s.i.CustomAsyncImpl.(): === async end ===
可以发现,该方法使用了线程池.
扩展:打印线程池情况,占用性能
- 编写
VisibleThreadPoolTaskExecutor
继承ThreadPoolTaskExecutor
类
/**
* 可见的线程池任务执行器,继承自ThreadPoolTaskExecutor,增强了线程池执行任务时的日志记录功能。
*/
public class VisibleThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory
.getLogger(VisibleThreadPoolTaskExecutor.class);
/**
* 打印线程池当前状态的方法。
*
* @param method 当前调用的方法名,用于日志标记。
*/
private void log(String method) {
ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();
// 打印线程池关键信息到日志,包括线程名前缀、调用方法、任务总数、完成任务数、活跃线程数和队列长度。
logger.info("线程池:{}, 执行方法:{},任务数量 [{}], 完成任务数量 [{}], 活跃线程数 [{}], 队列长度 [{}]",
this.getThreadNamePrefix(),
method,
threadPoolExecutor.getTaskCount(),
threadPoolExecutor.getCompletedTaskCount(),
threadPoolExecutor.getActiveCount(),
threadPoolExecutor.getQueue().size());
}
/**
* 重写execute方法,记录执行日志。
*
* @param task 将要执行的任务对象。
*/
@Override
public void execute(Runnable task) {
log("execute");
super.execute(task);
}
/**
* 重写带启动超时的execute方法,记录执行日志。
*
* @param task 将要执行的任务对象。
* @param startTimeout 启动任务的超时时间。
*/
@Override
public void execute(Runnable task, long startTimeout) {
log("execute");
super.execute(task, startTimeout);
}
/**
* 重写submit方法,记录执行日志。
*
* @param task 将要提交的任务对象。
* @return 返回Future对象,代表异步执行的结果。
*/
@Override
public Future<?> submit(Runnable task) {
log("submit");
return super.submit(task);
}
/**
* 重写submit方法,记录执行日志。
*
* @param task 将要提交的带返回值的任务对象。
* @param <T> 返回值的类型。
* @return 返回Future对象,代表异步执行的结果。
*/
@Override
public <T> Future<T> submit(Callable<T> task) {
log("submit");
return super.submit(task);
}
/**
* 重写submitListenable方法,记录执行日志。
*
* @param task 将要提交的任务对象。
* @return 返回ListenableFuture对象,代表异步执行的结果。
*/
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
log("submitListenable");
return super.submitListenable(task);
}
/**
* 重写submitListenable方法,记录执行日志。
*
* @param task 将要提交的带返回值的任务对象。
* @param <T> 返回值的类型。
* @return 返回ListenableFuture对象,代表异步执行的结果。
*/
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
log("submitListenable");
return super.submitListenable(task);
}
}
- 重新编写线程池配置类
// 开启异步支持的配置类
@EnableAsync
// 配置类
@Configuration
public class ThreadPoolConfig {
// 自动注入线程池属性配置
@Autowired
private ThreadPoolProperties threadPoolProperties;
@Bean(name = "visibleTaskExecutor")
public Executor visible() {
// 使用VisibleThreadPoolTaskExecutor作为类
ThreadPoolTaskExecutor executor = new VisibleThreadPoolTaskExecutor();
executor.setCorePoolSize(threadPoolProperties.getCorePoolSize());
executor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize());
executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
executor.setThreadNamePrefix(threadPoolProperties.getThreadNamePrefix());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
/**
* 创建并配置一个线程池任务执行器。
*
* @return Executor 返回配置好的线程池任务执行器实例。
*/
@Bean(name = "taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 根据配置设定线程池的核心大小
executor.setCorePoolSize(threadPoolProperties.getCorePoolSize());
// 根据配置设定线程池的最大大小
executor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize());
// 根据配置设定线程池队列的容量
executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
// 根据配置设定线程的保持活跃时间
executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
// 设置线程名称前缀
executor.setThreadNamePrefix(threadPoolProperties.getThreadNamePrefix());
// 线程池关闭时等待所有任务完成再销毁
executor.setWaitForTasksToCompleteOnShutdown(true);
// 设置拒绝执行处理器为CallerRunsPolicy,即调用者运行策略:任务超出线程池容量后,新任务交还主线程处理
// 当提交的线程填满核心线程数,并且塞满了队列缓冲区,并且超过了最大线程数时就会触发拒绝策略;
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
- service和实现类也重新编写
void visibleAsync();
/**
* 可视化,可以打印线程池情况
*/
@Override
@Async("visibleTaskExecutor")
public void visibleAsync() {
logger.info("=== async start ===");
logger.info("线程: {}执行代码逻辑", Thread.currentThread().getName());
logger.info("=== async end ===");
}
- 测试
@SpringBootTest
public class CustomAsyncTest {
@Autowired
private ICustomAsync customAsync;
@Test
void visibleAsyncTests() {
for (int i = 0; i < 20; i++) {
customAsync.visibleAsync();
}
}
}
- 返回结果
2024-03-28 11:23:05,406 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [0], 完成任务数量 [0], 活跃线程数 [0], 队列长度 [0]
2024-03-28 11:23:05,407 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [1], 完成任务数量 [0], 活跃线程数 [1], 队列长度 [0]
2024-03-28 11:23:05,407 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [2], 完成任务数量 [0], 活跃线程数 [2], 队列长度 [0]
2024-03-28 11:23:05,408 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [3], 完成任务数量 [0], 活跃线程数 [3], 队列长度 [0]
2024-03-28 11:23:05,408 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [4], 完成任务数量 [0], 活跃线程数 [4], 队列长度 [0]
2024-03-28 11:23:05,409 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [5], 完成任务数量 [0], 活跃线程数 [5], 队列长度 [0]
2024-03-28 11:23:05,409 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [6], 完成任务数量 [0], 活跃线程数 [6], 队列长度 [0]
2024-03-28 11:23:05,409 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [7], 完成任务数量 [0], 活跃线程数 [7], 队列长度 [0]
2024-03-28 11:23:05,409 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [8], 完成任务数量 [0], 活跃线程数 [8], 队列长度 [0]
2024-03-28 11:23:05,409 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [9], 完成任务数量 [0], 活跃线程数 [9], 队列长度 [0]
2024-03-28 11:23:05,414 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [10], 完成任务数量 [0], 活跃线程数 [10], 队列长度 [0]
2024-03-28 11:23:05,415 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [11], 完成任务数量 [0], 活跃线程数 [10], 队列长度 [1]
2024-03-28 11:23:05,415 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [12], 完成任务数量 [0], 活跃线程数 [10], 队列长度 [2]
2024-03-28 11:23:05,416 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [13], 完成任务数量 [0], 活跃线程数 [10], 队列长度 [3]
2024-03-28 11:23:05,417 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [14], 完成任务数量 [0], 活跃线程数 [10], 队列长度 [4]
2024-03-28 11:23:05,417 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [15], 完成任务数量 [0], 活跃线程数 [10], 队列长度 [5]
2024-03-28 11:23:05,423 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [16], 完成任务数量 [0], 活跃线程数 [10], 队列长度 [6]
2024-03-28 11:23:05,425 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [17], 完成任务数量 [0], 活跃线程数 [10], 队列长度 [7]
2024-03-28 11:23:05,427 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [18], 完成任务数量 [0], 活跃线程数 [10], 队列长度 [8]
2024-03-28 11:23:05,430 INFO [main] c.k.b.c.VisibleThreadPoolTaskExecutor.(): 线程池:thread-pool-executor-, 执行方法:submit,任务数量 [19], 完成任务数量 [0], 活跃线程数 [10], 队列长度 [9]
2024-03-28 11:23:05,431 INFO [thread-pool-executor-1] c.k.b.s.i.CustomAsyncImpl.(): === async start ===
2024-03-28 11:23:05,432 INFO [thread-pool-executor-1] c.k.b.s.i.CustomAsyncImpl.(): 线程: thread-pool-executor-1执行代码逻辑
2024-03-28 11:23:05,432 INFO [thread-pool-executor-1] c.k.b.s.i.CustomAsyncImpl.(): === async end ===
2024-03-28 11:23:05,433 INFO [thread-pool-executor-1] c.k.b.s.i.CustomAsyncImpl.(): === async start ===
2024-03-28 11:23:05,435 INFO [thread-pool-executor-1] c.k.b.s.i.CustomAsyncImpl.(): 线程: thread-pool-executor-1执行代码逻辑
2024-03-28 11:23:05,441 INFO [thread-pool-executor-1] c.k.b.s.i.CustomAsyncImpl.(): === async end ===
2024-03-28 11:23:05,441 INFO [thread-pool-executor-1] c.k.b.s.i.CustomAsyncImpl.(): === async start ===
2024-03-28 11:23:05,441 INFO [thread-pool-executor-1] c.k.b.s.i.CustomAsyncImpl.(): 线程: thread-pool-executor-1执行代码逻辑
......
每次执行的时候,都会打印对应的线程池情况了
线程池配置推荐
常用线程池
commonTaskExecutor 该线程池核心线程20,缓存队列60,最大线程100。
核心线程满载之后,新任务会先放到缓存队列中。适用于触发频率高、耗时短的任务。因为这种任务会迅速释放掉核心线程,然后继续消化缓存队列中的任务。这样既能保证队列中任务不会等太久,也降低了频繁创建销毁线程的资源开销。
长耗时线程池
对于耗时长的任务,如果使用常用线程池处理,会存在耗时长任务逐渐打满线程池负载,而触发频率高、耗时短的任务没有空闲线程可用的可能性。因此有必要单独为耗时长的任务配置一个线程池。
expensiveTaskExecutor 该线程池核心线程20,缓存队列0,最大线程100。
核心线程满载之后,会立即开启新线程。适用于触发频率低、耗时长的任务。因为这类任务占用单个线程时间长,缓存队列为0,有利于缩短任务平均执行时间。
注:以上两个线程池配置,是基于网络模块自身业务特征,在实践中总结出来的,并不完全适用于其他模块,请根据各自模块业务特征进行微调。
FAQ
Spring的线程池ThreadPoolTaskExecutor
,和JDK自带线程池ThreadPoolExecutor
的区别是什么?
ThreadPoolTaskExecutor
在ThreadPoolExecutor
的基础上进行了一层封装,增加了submitListenable
方法。
而submitListenable
方法返回的ListenableFuture
接口对象,可以用来添加线程执行完毕后成功和失败的回调方法。
与Future.get()
方法相比,通过回调的方式,不会阻塞主线程,线程执行结果或抛出的异常通过回调函数异步处理。
示例代码如下:
final ListenableFuture<String> listenableFuture = threadPoolTaskExecutor
.submitListenable(() -> {
TimeUnit.SECONDS.sleep(2);
return "result";
});
listenableFuture.addCallback(data -> log.info(data), e -> log.error(e.getMessage(), e));
是否有办法动态调整线程池配置?
可以将线程池的三个核心参数corePoolSize
、queueCapacity
、maxPoolSize
,配置到配置中心中,项目内线程池从配置中心中取这个配置。