Spring線程池知多少?-------ThreadPoolTaskExecutor類源碼淺析

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容器中沒有ThreadPoolTaskExecutorExecutor的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。
  1. 如果此時線程池中的數量小於corePoolSize,即使線程池中的線程都處於空閒狀態,也要創建新的線程來處理被添加的任務。

  2. 如果此時線程池中的數量等於 corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。

  3. 如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量小於maxPoolSize,建新的線程來處理被添加的任務。

  4. 如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量等於maxPoolSize,那麼通過handler所指定的策略來處理此任務。也就是:處理任務的優先級爲:核心線程corePoolSize、任務隊列workQueue、最大線程
    maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。

  5. 當線程池中的線程數量大於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實例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章