SpringBoot配置线程池
一.线程池简介
1.1.什么是线程池
线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。在JAVA中主要是使用ThreadPoolExecutor类来创建线程池,并且JDK中也提供了Executors工厂类来创建线程池(不推荐使用)。
1.2.线程池的优点
降低资源消耗,复用已创建的线程来降低创建和销毁线程的消耗。
提高响应速度,任务到达时,可以不需要等待线程的创建立即执行。
提高线程的可管理性,使用线程池能够统一的分配、调优和监控。
1.3.线程池五种状态
线程池ThreadPoolExecutor分别有五种状态分别为:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED使用一个AtomicInteger类型的ctl字段来描述线程池地运行状态和线程数量,通过ctl的高三位来表示线程池的5种状态,低29位表示线程池中现有的线程数量。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
/**
* 线程池线程数地bit数
*/
private static final int COUNT_BITS = Integer.SIZE - 3;
/**
* 线程池中最大线程容量
*/
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
/**
* 表示可接受新任务,且可执行队列中的任务;
*/
private static final int RUNNING = -1 << COUNT_BITS;
/**
* 表示不接受新任务,但可执行队列中的任务;
*/
private static final int SHUTDOWN = 0 << COUNT_BITS;
/**
* 表示不接受新任务,且不再执行队列中的任务,且中断正在执行的任务;
*/
private static final int STOP = 1 << COUNT_BITS;
/**
* 所有任务已经中止,且工作线程数量为0,最后变迁到这个状态的线程将
* 要执行terminated()钩子方法,只会有一个线程执行这个方法;
*/
private static final int TIDYING = 2 << COUNT_BITS;
/**
* TERMINATED,中止状态,已经执行完terminated()钩子方法;
*/
private static final int TERMINATED = 3 << COUNT_BITS;
线程池状态之间的流转图如下所示
1.4.线程池处理流程
线程池处理流程代码如下所示
/**
* 在将来某个时候执行给定的任务。该任务可以在新线程中执行,也可以在现有的池线程中执行。如果由于此执行
* 器已关闭或已达到其容量,任务无法提交执行,则由当前RejectedExecutionHandler处理该任务。
* Params:
* 命令–要执行的任务
* Throws:
* RejectedExecutionException–如果任务无法接受执行,则由RejectedExecutionHandler自行决定
* NullPointerException–如果命令为null
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
分三步走:
1.如果运行的线程少于corePoolSize,请尝试以给定的命令作为第一个任务来启动一个新线程。
对addWorker的调用原子地检查runState和workerCount,因此,通过返回false,可以防止
在不应该添加线程的情况下添加线程的错误警报。
2.如果一个任务可以成功排队,那么我们仍然需要仔细检查我们是否应该添加一个线程(因为自上
次检查以来已有的线程已经失效),或者池是否在进入该方法后关闭。因此,如果有必要,我们重
新检查状态,如果停止,则回滚排队,如果workerCount为0,则启动一个新线程。
3.如果我们不能对任务进行排队,那么我们尝试添加一个新线程。如果它失败了,我们知道线程池
被关闭或饱和了,所以拒绝了这项任务。
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
} else if (!addWorker(command, false))
reject(command);
}
线程池处理流程图如下所示
二.开始配置
ThreadPoolTaskExecutor是spring core包中的,而ThreadPoolExecutor是JDK中的JUC。ThreadPoolTaskExecutor是对ThreadPoolExecutor进行了封装处理。
2.1.依赖导入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2.2.配置application.properties文件
universe.thread.pool.executor.threadNamePrefix=threadPoolTaskExecutor-
universe.thread.pool.executor.queueCapacity=100
universe.thread.pool.executor.rejectedExecutionHandler=java.util.concurrent.ThreadPoolExecutor$AbortPolicy
universe.thread.pool.executor.keepAliveSeconds=60
2.3.创建PoolExecutorConfig.java
package com.temperature.humidity.system.config.thread;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@Data
@Log4j2
public class PoolExecutorConfig {
/**
* 获取CPU核数
*/
private static final int CPU_NUM;
/**
* 配置线程池的前缀
*/
@Value("${universe.thread.pool.executor.threadNamePrefix}")
private String threadNamePrefix;
/**
* 线程池的核心线程数。在没有设置 allowCoreThreadTimeOut 为 true 的情况下,
* 核心线程会在线程池中一直存活,即使处于闲置状态。
*/
private Integer corePoolSize;
/**
* 线程池中的任务队列,通过线程池的 execute() 方法提交的 Runnable
* 对象会存储在该队列中。
*/
@Value("${universe.thread.pool.executor.queueCapacity}")
private Integer queueCapacity;
/**
* 线程池所能容纳的最大线程数。当活动线程(核心线程+非核心线程)达到这个数值后,
* 后续任务将会根据 RejectedExecutionHandler 来进行拒绝策略处理。
*/
private Integer maxPoolSize;
/**
* 当任务无法被执行时(超过线程最大容量 maximum 并且 workQueue 已经被排满了)的处理策略,
* - AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
* - DiscardPolicy:丢弃任务,但是不抛出异常。
* - DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
* - CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
*/
@Value("${universe.thread.pool.executor.rejectedExecutionHandler}")
private String rejectedExecutionHandler;
/**
* 非核心线程 闲置时的超时时长。超过该时长,非核心线程就会被回收。若线程池通设置
* 核心线程也允许 timeOut,即 allowCoreThreadTimeOut 为 true,则该时长
* 同样会作用于核心线程,在超过aliveTime 时,核心线程也会被回收,AsyncTask
* 配置的线程池就是这样设置的。
*/
@Value("${universe.thread.pool.executor.keepAliveSeconds}")
private Integer keepAliveSeconds;
public PoolExecutorConfig() {
log.info("该台服务器的CPU核心数为{}", CPU_NUM);
this.corePoolSize = CPU_NUM + 1;
this.maxPoolSize = 2 * CPU_NUM;
}
static {
CPU_NUM = Runtime.getRuntime().availableProcessors();
}
}
2.4.创建OurPoolExecutor.java
package com.temperature.humidity.system.config.thread;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionHandler;
/**
* @description 线程池配置类
*/
@Configuration
@Log4j2
public class OurPoolExecutor {
@Autowired
private PoolExecutorConfig threadPoolExecutorConfig;
@Bean
public Executor threadPoolTaskExecutor() {
String threadNamePrefix = threadPoolExecutorConfig.getThreadNamePrefix();
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
try {
log.info("开始初始化线程池 -->threadPoolTaskExecutor");
taskExecutor.setThreadNamePrefix(threadNamePrefix);
taskExecutor.setCorePoolSize(threadPoolExecutorConfig.getCorePoolSize());
taskExecutor.setMaxPoolSize(threadPoolExecutorConfig.getMaxPoolSize());
taskExecutor.setQueueCapacity(threadPoolExecutorConfig.getQueueCapacity());
//通过反射获取RejectedExecutionHandlerClass 的类模板
Class<?> rejectedExecutionHandlerClass = Class.forName(threadPoolExecutorConfig.getRejectedExecutionHandler());
//获取RejectedExecutionHandlerClass类的实例
RejectedExecutionHandler rejectedExecutionHandler = (RejectedExecutionHandler) rejectedExecutionHandlerClass.newInstance();
taskExecutor.setRejectedExecutionHandler(rejectedExecutionHandler);
taskExecutor.setKeepAliveSeconds(threadPoolExecutorConfig.getKeepAliveSeconds());
//进行加载
taskExecutor.initialize();
log.info("初始化线程池完成:{}核心线程为{}-->", threadNamePrefix, taskExecutor.getCorePoolSize());
return taskExecutor;
} catch (Exception e) {
e.printStackTrace();
log.error("初始化线程池失败:{}失败原因为:{}", threadNamePrefix, e.getMessage());
return null;
}
}
}
三.测试
我们配置线程池后启动我们的SpringBoot项目,可以看到我们的控制台打印出来了如下图中圈中的日志,测试成功。