0. 環境
本文所示案例爲SpringBoot 2.2.2.RELEASE所搭載的maven web項目。pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>JavaLearn</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
</project>
spring-boot-starter-parent父pom會引入一個artifactId爲spring-context的jar包。此jar包爲Spring項目的核心Core包之一,搭建Spring或SpringBoot項目都會引入此jar包。
而我們這一節的重點ThreadPoolTaskExecutor:,就在此jar包的org.springframework.scheduling.concurrent路徑下
1. Spring自帶線程池源碼分析
下面是SpringBoot 2.2.2.RELEASE在啓動時的輸出日誌。可以看到Spring容器啓動了一個name爲applicationTaskExecutor的bean。
定位此bean的創建過程,其代碼放在spring-boot-autoconfigure模塊內。創建此bean的@Configuration類如下:
//整個Configuration類有條件的加載,當Spring容器內缺失ThreadPoolTaskExecutor對象bean才生效
@ConditionalOnClass({ThreadPoolTaskExecutor.class})
@Configuration(
proxyBeanMethods = false
)
@EnableConfigurationProperties({TaskExecutionProperties.class})
public class TaskExecutionAutoConfiguration {
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
public TaskExecutionAutoConfiguration() {
}
//創建Builder構建類
@Bean
@ConditionalOnMissingBean
public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties, ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers, ObjectProvider<TaskDecorator> taskDecorator) {
Pool pool = properties.getPool();
TaskExecutorBuilder builder = new TaskExecutorBuilder();
builder = builder.queueCapacity(pool.getQueueCapacity());
builder = builder.corePoolSize(pool.getCoreSize());
builder = builder.maxPoolSize(pool.getMaxSize());
builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
builder = builder.keepAlive(pool.getKeepAlive());
Shutdown shutdown = properties.getShutdown();
builder = builder.awaitTermination(shutdown.isAwaitTermination());
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
Stream var10001 = taskExecutorCustomizers.orderedStream();
var10001.getClass();
builder = builder.customizers(var10001::iterator);
builder = builder.taskDecorator((TaskDecorator)taskDecorator.getIfUnique());
return builder;
}
//向容器內注入ThreadPoolTaskExecutor實例對象bean
@Lazy
@Bean(
name = {"applicationTaskExecutor", "taskExecutor"}
)
@ConditionalOnMissingBean({Executor.class})
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
return builder.build();
}
}
可以看出,當Spring容器中沒有ThreadPoolTaskExecutor與Executor的bean實例加載時,Spring會默認創建一個ThreadPoolTaskExecutor類的實例,並注入Spring容器內。
2. 自帶線程池配置項
從上面代碼看到,在創建ThreadPoolTaskExecutor時,用到了TaskExecutorBuilder的bean實例:builder,此bean正好在上面的@bean方法public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties, ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers, ObjectProvider<TaskDecorator> taskDecorator)
中有定義。
builder使用到了TaskExecutionProperties 配置項,點擊打開可見:
@ConfigurationProperties("spring.task.execution")
public class TaskExecutionProperties {
private final Pool pool = new Pool();
private final Shutdown shutdown = new Shutdown();
/**
* Prefix to use for the names of newly created threads.
*/
private String threadNamePrefix = "task-";
//省略若干setters、getters
......
public static class Pool {
/**
* Queue capacity. An unbounded capacity does not increase the pool and therefore
* ignores the "max-size" property.
*/
private int queueCapacity = Integer.MAX_VALUE;
/**
* Core number of threads.
*/
private int coreSize = 8;
/**
* Maximum allowed number of threads. If tasks are filling up the queue, the pool
* can expand up to that size to accommodate the load. Ignored if the queue is
* unbounded.
*/
private int maxSize = Integer.MAX_VALUE;
/**
* Whether core threads are allowed to time out. This enables dynamic growing and
* shrinking of the pool.
*/
private boolean allowCoreThreadTimeout = true;
/**
* Time limit for which threads may remain idle before being terminated.
*/
private Duration keepAlive = Duration.ofSeconds(60);
//省略若干setters、getters
......
}
//省略class ShutDown
......
}
由源碼可知:Spring創建自帶的線程池ThreadPoolTaskExecutor對象時,會讀取配置項:spring.task.execution.pool.* 並創建線程池。
(這也就是Spring Boot可以在application.properties文件中使用這些配置,來指定線程池參數的原因)。
具體參數如下:
- spring.task.execution.pool.core-size # 核心線程數,默認爲8
- spring.task.execution.pool.queue-capacity # 隊列容量,默認爲無限大
- spring.task.execution.pool.max-size # 最大線程數,默認爲無限大
- spring.task.execution.pool.allow-core-thread-timeout #
是否允許回收空閒的線程,默認爲true - spring.task.execution.pool.keep-alive #空閒的線程可以保留多少秒,默認爲60。如果超過這個時間沒有任務調度,則線程會被回收
- spring.task.execution.thread-name-prefix # 線程名前綴,默認爲thread-
具體邏輯爲:
1. 如果當前要執行的任務數超過core-size,則任務會放到隊列裏面等待執行,等核心線程中有任務執行完成之後,再取出隊列中的任務進行調度執行。
2. 如果等待隊列已經滿了,再收到新任務時,則核心線程會自動擴容,最大擴展到max-size。
如果此時線程池中的數量小於corePoolSize,即使線程池中的線程都處於空閒狀態,也要創建新的線程來處理被添加的任務。
如果此時線程池中的數量等於 corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。
如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量小於maxPoolSize,建新的線程來處理被添加的任務。
如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量等於maxPoolSize,那麼通過handler所指定的策略來處理此任務。也就是:處理任務的優先級爲:核心線程corePoolSize、任務隊列workQueue、最大線程
maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。當線程池中的線程數量大於corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態的調整池中的線程數。
3. 自定義線程池ThreadPoolTaskExecutor
由上面的源碼可以看出,如果用戶自定義ThreadPoolTaskExecutor的bean,則可以替換Spring的原生TaskExecutor類,起到自定義線程池的效果
@Configuration
public class TaskConfiguration {
//定義自己的線程池,name爲myThreadPoolTaskExecutor
@Bean
public ThreadPoolTaskExecutor myThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
return executor;
}
}
重新啓動Spring Boot,日誌如下:
可以看出不再啓動Spring自帶的bean:applicationTaskExecutor,而是啓動了我們重新定義的bean:myThreadPoolTaskExecutor
4. TaskExecutor實例
Spring自帶的TaskExecutor實例很多,看基本繼承結構如下:
常用的TaskExecutor實例用法可參考:《Spring+TaskExecutor實例》