线程池参数设置原则

  为了减少创建和销毁线程的次数,让每个线程都可以多次的使⽤,可以根据系统情况调整线程的数量,防⽌消耗过多内存。在实际使⽤

中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当⼤,使⽤线程池就可以优化。

  在java中,如果每个请求到达就创建⼀个新线程,开销是相当⼤的。在实际使⽤中,服务器在创建和销毁线程上花费的时间和消耗的系

统资源都相当⼤,甚⾄可能要⽐在处理实际的⽤户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系

统资源。如果在⼀个jvm⾥创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”⽽导致系统资源不⾜。为了防⽌资源不⾜,服务器

应⽤程序需要采取⼀些办法来限制任何给定时刻处理的请求数⽬,尽可能减少创建和销毁线程的次数,特别是⼀些资源耗费⽐较⼤的线程的

创建和销毁,尽量利⽤已有对象来进⾏服务,这就是“池化资源”技术产⽣的原因。

线程池主要⽤来解决线程⽣命周期开销问题和资源不⾜问题。通过对多个任务重复使⽤线程,线程创建的开销就被分摊到了多个任务上

了,⽽且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以⽴即为请求服务,使⽤应⽤程序响应更快。另

外,通过适当的调整线程中的线程数⽬可以防⽌出现资源不⾜的情况。

⼀:线程池参数简介

ThreadPoolExecutor类可设置的参数主要有:

corePoolSize:核⼼线程

1.核⼼线程会⼀直存活,及时没有任务需要执⾏

2.当线程数⼩于核⼼线程数时,即使有线程空闲,线程池也会优先创建新线程处理

3.设置allowCoreThreadTimeout=true(默认false)时,核⼼线程会超时关闭

queueCapacity:任务队列容量(阻塞队列)

当核⼼线程数达到最⼤时,新任务会放在队列中排队等待执⾏

maxPoolSize:最⼤线程数

1.当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务

2.当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务⽽抛出异常

keepAliveTime:线程空闲时间

1.当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize

2.如果allowCoreThreadTimeout=true,则会直到线程数量=0

rejectedExecutionHandler:任务拒绝处理器

两种情况会拒绝处理任务:

1.当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务

2.当线程池被调⽤shutdown()后,会等待线程池⾥的任务执⾏完毕,再shutdown。如果在调⽤shutdown()和线程池真正shutdown之间提交

任务,会拒绝新任务。

线程池会调⽤rejectedExecutionHandler来处理这个任务。如果没有设置,默认是AbortPolicy,会抛出异常。

ThreadPoolExecutor类有⼏个内部实现类来处理拒绝任务:

1.AbortPolicy 丢弃任务,抛运⾏时异常

2.CallerRunsPolicy 执⾏任务

3.DiscardPolicy 忽视,什么都不会发⽣

4.DiscardOldestPolicy 从队列中踢出最先进⼊队列(最后⼀个执⾏)的任务

5.实现RejectedExecutionHandler接⼝,可⾃定义处理器

⼆:ThreadPoolExecutor执⾏顺序:

1.当线程数⼩于核⼼线程数时,创建线程。

2.当线程数⼤于等于核⼼线程数,且任务队列未满时,将任务放⼊任务队列。

3.当线程数⼤于等于核⼼线程数,且任务队列已满

3.1若线程数⼩于最⼤线程数,创建线程

3.2若线程数等于最⼤线程数,抛出异常,拒绝任务

三:线程池参数的合理设置

为了说明合理设置的条件,我们⾸先确定有以下⼏个相关参数:

1.tasks,程序每秒需要处理的最⼤任务数量(假设系统每秒任务数为100~1000)

2.tasktime,单线程处理⼀个任务所需要的时间(每个任务耗时0.1秒)

3.responsetime,系统允许任务最⼤的响应时间(每个任务的响应时间不得超过2秒)

corePoolSize

每个任务需要tasktime秒处理,则每个线程每秒可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:

tasks/(1/tasktime)。

即tasks*tasktime个线程数。假设系统每秒任务数为100到1000之间,每个任务耗时0.1秒,则需要100x0.1⾄1000x0.1,即10到100个线程。

那么corePoolSize应该设置为⼤于10。

具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情况下任务数⼩于200,最多时为1000,则corePoolSize可设置

为20。

queueCapacity:任务队列的长度:

任务队列的长度要根据核⼼线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime)responsetime(20/0.1)2=400,即队列长度可设置为400。

如果队列长度设置过⼤,会导致任务响应时间过长,如以下写法:

LinkedBlockingQueue queue = new LinkedBlockingQueue();

这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响

应时间也将随之陡增。

maxPoolSize:最⼤线程数

当系统负载达到最⼤值时,核⼼线程数已⽆法按时处理完所有任务,这时就需要增加线程。每秒200个任务需要20个线程,那么当每秒达到

1000个任务时,则需要(1000-queueCapacity)*(20/200),即60个线程,可将maxPoolSize设置为60。

keepAliveTime:

线程数量只增加不减少也不⾏。当负载降低时,可减少线程数量,如果⼀个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下

线程池最少会保持corePoolSize个线程。keepAliveTiime设定值可根据任务峰值持续时间来设定。

以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,⽐如,当线程数量达到50时,CPU达到100%,则将maxPoolSize设

置为60也不合适,此时若系统负载长时间维持在每秒1000个任务,则超出线程池处理能⼒,应设法降低每个任务的处理时间(tasktime)。