1.SequencepublicstaticExecutorServicenewThreadPool(){returnnewThreadPoolExecutor(30,60,60L,TimeUnit.MILLISECONDS,newLinkedBlockingQueue());}我们今天来借用这个问题说说线程池中维护的线程。它的增长和回收策略是什么?二、线程池策略2.1线程池的参数当我们讲线程池中线程的增长策略的时候,最引人注目的就是它的核心线程数(corePoolSize)和最大线程数(maximumPoolSize),但只看这两个参数还不够全面。线程数的增加还与任务等待队列有关。下面看一下参数最全的ThreadPoolExecutor的构造方法:publicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler){//...}简单解释一下各个参数的含义:corePoolSize:核心线程数;maximumPoolSize:线程池最大线程数;keepAliveTime:除核心线程数以外的线程,最大空闲存活时间;unit:keepAliveTime的时间单位;workQueue:线程池的任务等待队列;threadFractory:线程工厂,用于为线程池创建线程;handler:拒绝策略,线程池无法处理任务时的拒绝方法;其中许多参数的配置相互影响。比如任务等待队列workQueue配置不当,可能会导致线程池中的线程一直增长不到核心线程数(maximumPoolSize)配置的线程数。2.2线程池中线程的增长策略看到这里大家应该清楚了,线程池线程的增长策略与3个参数有关:corePoolSize:核心线程数maximumPoolSize:最大线程数;workQueue:等待任务队列;之前他们的关系是这样的:接下来,我们来看一下线程池中线程的理想增长策略。默认情况下,线程池最初是空的。当有新任务到来时,线程池开始通过线程工厂(threadFractory)创建线程来处理任务。新任务会不断触发线程池中线程的创建,直到线程数达到核心线程数(corePoolSize),然后停止创建线程,将新任务放入任务等待队列(工作队列)。新任务不断进入任务等待队列。当队列满时,重新创建线程处理任务,直到线程池中的线程数达到maximumPoolSize配置。此时线程池中的线程数达到最大值,没有空闲线程,任务队列中任务满了。这时候如果有新的任务进来,就会触发线程池的拒绝策略(handler)。如果没有配置拒绝策略,将抛出RejectedExecutionException。至此,线程的增长策略就清晰了,我们可以通过下图了解完整的流程。最重要的是任务的等待队列。不管等待队列是什么实现结构,只有当它满了,线程池中的线程才会增加到最大线程数。但是对于一个可以满的队列来说,它的前提一定是一个有界队列。这就是文章开头给出的例子中隐藏的坑。让我们回顾一下前面构建的线程池。publicstaticExecutorServicenewThreadPool(){returnnewThreadPoolExecutor(30,60,60L,TimeUnit.MILLISECONDS,newLinkedBlockingQueue());}可以看到这里的最大线程数虽然大于核心线程数,但是它的等待队列配置是一个LinkedBlockingQueue,从名字就可以看出这是一个基于链表的阻塞队列,在使用其默认构造方法构造时,其容量设置为Integer.MAX_VALUE,可以简单理解为无界队列。publicLinkedBlockingQueue(){this(Integer.MAX_VALUE);}publicLinkedBlockingQueue(intcapacity){if(capacity<=0)thrownewIllegalArgumentException();this.capacity=capacity;last=head=newNode(null);}这就是为什么,这样构建的线程池,核心线程数的配置参数,永远不会被使用,因为它的等待队列永远不会满。2.3线程池中线程的收缩策略线程池中执行的任务总是执行完就结束。那么,当线程池中存在大量空闲线程时,线程池也会有一定的收缩策略,回收线程池中的冗余线程。线程池中的线程缩水策略与以下参数有关:corePoolSize:核心线程数;maximumPoolSize:线程池最大线程数;keepAliveTime:非核心线程数的线程空闲存活时间;unit:keepAliveTime的时间单位;corePoolSize和maximumPoolSize我们都很熟悉,另外一个可以控制的就是keepAliveTime,空闲生存时间,这个时间的单位。当线程池中的线程数超过核心线程数时。这时候如果任务量减少,肯定会有一些线程处于空闲状态,没有任务执行。那么如果这个线程的空闲时间超过了keepAliveTime&unit配置的持续时间,就会被回收。需要注意的是,对于线程池来说,它只是负责管理线程。对于创建的线程,不区分所谓的“核心线程”和“非核心线程”。它只管理线程池中的线程总数。当回收的线程数达到corePoolSize时,回收过程就会停止。还有一种方法是在线程池的核心线程数中回收线程,可以通过allowCoreThreadTimeOut(true)方法设置。当核心线程空闲时,一旦超过keepAliveTime&unit配置的时间,也会被回收。publicvoidallowCoreThreadTimeOut(booleanvalue){if(value&&keepAliveTime<=0)thrownewIllegalArgumentException("Corethreadsmusthavenonzerokeepalivetimes");if(value!=allowCoreThreadTimeOut){allowCoreThreadTimeOut=value;if(value)interruptIdleWorkers();}}如果keepAliveTime可以设置allowCoreThreadTimeOut()不能为0。2.3查漏补漏1.等待队列也会影响拒绝策略。如果将等待队列配置为无界队列,不仅会影响线程数从核心线程数增加到最大线程数,还会导致配置的拒绝策略永远持续下去。没有强制执行。因为只有当线程池中的工作线程数达到核心线程数,此时等待队列已满时,拒绝策略才能生效。2.核心线程数可以“预热”。前面说过,默认情况下,线程池中的线程是根据任务增长的。但是如果需要的话,我们也可以提前准备好线程池的核心线程,以应对突发性的高并发任务。比如在抢购系统中经常会有这样的需求。此时可以使用prestartCoreThread()或prestartAllCoreThreads()提前创建核心线程。这种方法称为“预热”。3.需要无界队列的场景怎么办?需求是多变的,我们肯定会遇到需要无界队列的场景,所以这个场景配置的maximumPoolSize是无效的。此时可以参考Executors中用newFixedThreadPool()创建线程池的过程,保持corePoolSize和maximumPoolSize一致。publicstaticExecutorServicenewFixedThreadPool(intnThreads){returnnewThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,newLinkedBlockingQueue());}此时核心线程数为最大线程数。以确保使用我们配置的线程数。4、线程池是否公平?所谓公平,就是先来的任务先执行。这在线程池中显然是不公平的。且不说线程池中的线程执行任务是由系统调度的,这就决定了任务的执行顺序无法保证,这是不公平的。另外,仅仅从线程池本身的角度来看,我们只看提交任务的顺序,这也是不公平的。首先,如果之前的任务已经分配了线程池的核心线程,此时任务就会进入任务队列。如果任务队列已满,新到达的任务将直接由线程池新创建的线程处理。直到线程数达到最大线程数。此时虽然任务队列中的任务先加入到线程池中等待处理,但是这些任务的处理时机要晚于后续新创建的线程要处理的任务,所以还是不公平,仅从任务的视角。3.总结时刻在这篇文章中,我们讲了线程池,线程数的增缩策略。这里我们简单总结一下:1.增长战略。默认情况下,线程池根据任务创建具有足够核心线程执行任务的线程,当核心线程满时将任务放入等待队列。当队列满了,继续创建新线程执行任务,直到达到最大线程数后停止。如果有新的任务,只能执行拒绝策略或者抛出异常。2.收缩策略。当线程池中的线程数大于核心线程数&&当前有空闲线程&&空闲线程的空闲时间大于keepAliveTime时,空闲线程会被回收,直到线程数等于核心线程数。简而言之,请记住谨慎使用无界队列。