Spring 的线程池执行器 ThreadPoolTaskExecutor 讲解
ThreadPoolTaskExecutor 是Spring 为我们封装的一个线程执行器,是以 Java 并发包提供的 ThreadTaskExecutor 类作为基础进行封装的。
ThreadPoolTaskExecutor 该类 Spring 提供了一下几个参数供用户自定义,当然不自定义的话都是有默认值的。
线程池中的核心线程数量,默认1
private int corePoolSize = 1;
最大线程数量,默认无穷大,建议自定义
private int maxPoolSize = Integer.MAX_VALUE;
线程空闲时间,默认一分钟
private int keepAliveSeconds = 60;
队列大小,默认无穷大,建议根据自己业务定义合适大小。
private int queueCapacity = Integer.MAX_VALUE;
ThreadFactory 线程工程,一般用于自定义线程名称,帮助定位问题,该属性定义在 父类ExecutorConfigurationSupport中,默认为当前类,当前类 ExecutorConfigurationSupport 继承自 CustomizableThreadFactory,而 CustomizableThreadFactory 继承自 ThreadFactory 线程工厂,所以这个类本身就是一个线程工厂。而ExecutorConfigurationSupport 继承的另一个类 CustomizableThreadCreator 就是帮助用户自定义 线程名称的,提供了属性 threadNamePrefix 用于用户自定义线程名称前缀 默认前缀为类名称 加 "-"
private ThreadFactory threadFactory = this;
private String threadNamePrefix;
return ClassUtils.getShortName(this.getClass()) + "-";
用户可以基于配置或代码自定义这些参数,Spring 定义了 set方法在用户自定义时注入对应自定义属性。
当然还提供了其他许多自定义属性,不常使用,所以就不讲解了。
配置或代码自定义示例代码如下:
@Configuration
public class ThreadPoolConfig {
@Bean(name = {"customThreadPoolTaskExecutor"})
public ThreadPoolTaskExecutor getCustomThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor customThreadPoolTaskExecutor = new ThreadPoolTaskExecutor();
customThreadPoolTaskExecutor.setMaxPoolSize(6);
customThreadPoolTaskExecutor.setCorePoolSize(4);
customThreadPoolTaskExecutor.setThreadNamePrefix("test_");
customThreadPoolTaskExecutor.setQueueCapacity(2000);
return customThreadPoolTaskExecutor;
}
}
该类持有 threadPoolExecutor,就是通过这个字段包装的 threadPoolExecutor。
private ThreadPoolExecutor threadPoolExecutor;
线程池的初始化是在什么时候进行的,即threadPoolExecutor 是在什么时候进行创建赋值的,我们跟踪源码探探究竟:
我们看到这个属性是在 这个 initializeExecutor 方法中进行赋值的,调用构造函数进行创建的:
@Override
protected ExecutorService initializeExecutor(
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
BlockingQueue queue = createQueue(this.queueCapacity);
ThreadPoolExecutor executor = new ThreadPoolExecutor(
this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
queue, threadFactory, rejectedExecutionHandler);
if (this.allowCoreThreadTimeOut) {
executor.allowCoreThreadTimeOut(true);
}
this.threadPoolExecutor = executor;
return executor;
}
这个方法又是被哪里调用的呢:
我们观察到这个类是被 ExecutorConfigurationSupport 父类 initialize 方法调用的:
public void initialize() {
if (logger.isInfoEnabled()) {
logger.info("Initializing ExecutorService " + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (!this.threadNamePrefixSet && this.beanName != null) {
setThreadNamePrefix(this.beanName + "-");
}
this.executor = initializeExecutor(this.threadFactory, this.rejectedExecutionHandler);
}
这个方法又是被同类中的 afterPropertiesSet 方法调用的 :
@Override
public void afterPropertiesSet() {
initialize();
}
看到 afterPropertiesSet 这个方法,相信属性 Spring 的同学都知道,这个是 Spring 提供的 Bean 初始化完成后需要进行后置操作的口子,可以 通过 实现 InitializingBean 接口,实现该方法完成自己的自定义操作。
而这个方法会被 Spring 容器在 当前 Bean 初始化完成后调起。至此我们就分析完成了这个 线程池执行器的初始化过程。
接下来,我们分析 几个核心参数的意义:
关于这个问题,我们可以分析一下如下的代码,这里是初始化线程执行器的地方:
@Override
protected ExecutorService initializeExecutor(
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
BlockingQueue queue = createQueue(this.queueCapacity);
ThreadPoolExecutor executor = new ThreadPoolExecutor(
this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
queue, threadFactory, rejectedExecutionHandler);
if (this.allowCoreThreadTimeOut) {
executor.allowCoreThreadTimeOut(true);
}
this.threadPoolExecutor = executor;
return executor;
}
protected BlockingQueue createQueue(int queueCapacity) {
if (queueCapacity > 0) {
return new LinkedBlockingQueue(queueCapacity);
}
else {
return new SynchronousQueue();
}
}
我们看到这些自定义的参数,除了 queueCapacity 外,其他的字段都是直接在 ThreadPoolExecutor 构造器中使用的,
queueCapacity 这个参数用于识别创建不同的队列和队列的容量,如果定义为大于 0 就是一个有界限队列,当然如果使用的是默认的配置 queueCapacity = Integer.MAX_VALUE 那就相当于是无界队列—所以建议都自定义该参数,因为线程的缓存是耗内存的(如果线程任务都是快速结束的任务,其实这个属性使用默认值也是无所谓的,但是如果线程任务比较重,任务并发高的话就不建议使用自定义的了),否则容量小于等于0 就是一个阻塞队列,只是起同步的作用,没有缓存的作用。
接下来这些参数具体什么含义就要分析 ThreadPoolExecutor 这个类了,
如上所说,这个类是 java 并发包下定义的一个类,
corePoolSize 表示核心线程数量,如果当前线程数量 小于该数量,有任务提交执行时,直接创建新的线程,
maxPoolSize 如果 当前线程数量大于 corePoolSize 小于 maxPoolSize 有任务提交执行时,直接创建新的线程
如果 当前线程数量 大于 maxPoolSize 提交的线程就会被 放到线程队列中等待执行。
如果等待线程大于队列大小,新提交的任务就走默认的或者自定义的 拒绝执行器。
所以如果 maxPoolSize 使用默认的无穷大,那么等待队列就不会被使用,当前执行线程数大于 corePoolSize 会一直创建新的线程执行任务,特别危险。
如果 maxPoolSize 没有定义无穷大,无法立即执行的任务就会到等待队列中执行,所以如果队列定义的无穷大,那么所有不能立即执行的任务都会到等待队列中,也是比较危险的事情。
分析到这里,不知道读者有没有发现,Spring 的这个包装类 其实就是支持用户自定义参数,用一个类封装了 java Executors 工具类的三个方法做的事情。
缓存线程执行器,核心线程数无穷大,所以会一直创建新的线程执行任务。队列为没有容量的公平队列,核心线程数量无穷大,不会使用 队列。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue(),
threadFactory);
ThreadPoolTaskExecutor 这个类,核心线程数量使用默认值,队列大小定义0 就是同样的效果。
单一线程执行器,核心和最大线程都是1,缓存队列无穷大,所以多余线程全部缓存,其他的进入等待队列等待执行。
ThreadPoolTaskExecutor 这个类,核心和最大线程都设置 1 ,队列大小使用默认值就是这个效果
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
threadFactory));
}
固定线程数量的线程执行器,最大核心线程 数量使用用户自定义的线程数量,队列使用无穷大。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
threadFactory);
}
ThreadPoolTaskExecutor 这个类,核心和最大线程都设置 同一个数量值 ,队列大小使用默认值就是这个效果