Spring 的线程池执行器 ThreadPoolTaskExecutor 讲解

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方法在用户自定义时注入对应自定义属性。

当然还提供了其他许多自定义属性,不常使用,所以就不讲解了。

配置或代码自定义示例代码如下:

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="4"/>
    <property name="maxPoolSize" value="6"/>
    <property name="queueCapacity" value="2000"/>
    <property name="threadNamePrefix" value="test"/>
</bean>
@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<Runnable> 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<Runnable> 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<Runnable> createQueue(int queueCapacity) {
   if (queueCapacity > 0) {
      return new LinkedBlockingQueue<Runnable>(queueCapacity);
   }
   else {
      return new SynchronousQueue<Runnable>();
   }
}

我们看到这些自定义的参数,除了 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<Runnable>(),
                                      threadFactory);

ThreadPoolTaskExecutor 这个类,核心线程数量使用默认值,队列大小定义0 就是同样的效果。

单一线程执行器,核心和最大线程都是1,缓存队列无穷大,所以多余线程全部缓存,其他的进入等待队列等待执行。

ThreadPoolTaskExecutor 这个类,核心和最大线程都设置 1 ,队列大小使用默认值就是这个效果

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory));
}

固定线程数量的线程执行器,最大核心线程 数量使用用户自定义的线程数量,队列使用无穷大。

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}

ThreadPoolTaskExecutor 这个类,核心和最大线程都设置 同一个数量值 ,队列大小使用默认值就是这个效果

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259