为什么要线程池

  • 一个线程分为创建时间、执行时间、销毁时间。很多情况下线程的创建时间+销毁时间比执行时间还长,造成了时间的浪费。
  • 所以与其等到任务来了再创建线程,还不如把若干线程提前创建好,等待任务的到来,直接开始执行。
  • 使用线程池还可以统一地对线程进行管理和调度。
  • 线程池可以提供队列,存放缓冲等待执行的任务。

线程池核心参数理解

理解线程池7个核心参数是理解线程池原理的前提,从对这7个参数的认识也可以明白线程池为什么能解决上述使用多线程时的问题。

corePoolSize

线程池中会维护一个最小的线程数量,即使这些线程都处于空闲状态,这里的最小线程数量即是corePoolSize。把其称为核心线程是因为它们正是前文所提到的“提前创建好”的线程。他们不会被销毁,除非设置了allowCoreThreadTimeOut,因此当它们中有处于空闲的线程,则这些线程等待任务的到来。一旦有任务则可以直接开始执行,省去创建线程的时间。

maximumPoolSize

这是线程池允许的最大线程数量。和上一个参数不同的是,这个参数规定了线程池所有线程的数量的总和的最大值。使用这个参数要结合线程池的工作流程进行说明,详见下文。

keepAliveTime

如果一个线程处于空闲状态,且目前线程池中的线程数大于corePoolSize,那么意味着进程池现在有多于核心进程数的进程在执行。既然规定了核心进程数显然就是希望在空闲时候不要有超过核心进程数的进程在池内,否则加大内存和维护的开销。因此这个参数规定的就是此进程(此进程空闲,且此时进程池的进程数大于核心进程数)在keepAliveTime时间后被销毁。

unit

指定keepAliveTime的时间单位

workQueue

工作队列,当一个任务被提交到进程池,且此时进程池内没有空闲的核心进程,就会先进入工作队列等待。进程池的工作流程详见下文。工作队列有以下几种:

  • ArrayBlockingQueue
    基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。
  • LinkedBlockingQuene
    基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。
  • SynchronousQuene
    一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务。
  • PriorityBlockingQueue
    具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

threadFactory:线程工厂

创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等。

handler:拒绝策略

当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,则执行拒绝策略。JDK中提供了四种拒绝策略:

  • CallerRunsPolicy
    在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
  • AbortPolicy
    直接丢弃任务,并抛出RejectedExecutionException异常。
  • DiscardPolicy
    直接丢弃任务,什么都不做。
  • DiscardOldestPolicy
    抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列

线程池的工作流程

通过前面对几个参数的认识,我们知道了线程池的几个关键的概念:核心线程数、最大线程数、工作队列、拒绝策略。线程池的工作流程就是围绕这几个概念展开的。

  1. 当任务提交到线程池,首先检查有没有核心线程处于空闲状态,如果有,则用该线程执行任务。若没有:
  2. 则将任务添加到工作队列的末尾,等待被调度执行。若此时工作队列也是满的,无法添加;
  3. 则查看进程池进程数是否已经到达了进程池的最大容量maximumPoolSize。若未到达,则创建一条新的进程执行任务。注意:此时执行的任务并非刚刚提交的任务,而应该是根据工作队列的调度顺序调度的任务。这样做以后就意味着工作队列有位置了,刚刚提交的任务就可以进队列了。而如果不幸的是此时进程池的进程数也到达了最大容量了,那么就意味着刚刚提交的任务既不能进队列,而队列中的任务也不可以及时被调度执行,那么新到来的任务“无处可去”,只能:
  4. 对新任务执行拒绝策略。

流程图如下所示:
进程池执行流程图

  • 注意:当工作队列是无界队列时,根据上述流程可知maximumPoolSize是不生效的。因为理论上队列无界,新任务总可以添加到队列末尾,不需要判断进程池内进程数是否小于maximumPoolSize,新来的任务总是“有容身之处”的。

常用进程池

  • FixThreadPool 固定数量的线程池,适用于对线程管理,高负载的系统
  • SingleThreadPool 只有一个线程的线程池,适用于保证任务顺序执行
  • CacheThreadPool 不限制线程数量的线程池,适用于执行短期异步任务的小程序,低负载系统
  • ScheduledThreadPool 定时任务使用的线程池,适用于定时任务