ThreadPoolExecutor线程池参数设置技巧
JDK1.5中引⼊了强⼤的concurrent包,其中最常⽤的莫过了线程池的实现ThreadPoolExecutor,它给我们带来了极⼤的⽅便,但同时,对于该线程池不恰当的设置也可能使其效率并不能达到预期的效果,甚⾄仅相当于或低于单线程的效率。
ThreadPoolExecutor类可设置的参数主要有:
corePoolSize
在创建了线程池后,默认情况下,线程池中并没有任何线程,⽽是等待有任务到来才创建线程去执⾏任务,(除⾮调⽤
了prestartAllCoreThreads()或者prestartCoreThread()⽅法,从这2个⽅法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者⼀个线程)。
默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建⼀个线程去执⾏任务,当线程池中的线程数⽬达到corePoolSize后,就会把到达的任务放到缓存队列当中。核⼼线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。
maxPoolSize
当线程数⼤于或等于核⼼线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能⼒,线程池会拒绝处理任务⽽抛出异常。
keepAliveTime
当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。
allowCoreThreadTimeout
是否允许核⼼线程空闲退出,默认值为false。
queueCapacity
线程池按以下⾏为执⾏任务
1. 当线程数⼩于核⼼线程数时,创建线程。
2. 当线程数⼤于等于核⼼线程数,且任务队列未满时,将任务放⼊任务队列。
3. 当线程数⼤于等于核⼼线程数,且任务队列已满
1. 若线程数⼩于最⼤线程数,创建线程
2. 若线程数等于最⼤线程数,抛出异常,拒绝任务
系统负载
参数的设置跟系统的负载有直接的关系,下⾯为系统负载的相关参数:
tasks,每秒需要处理的最⼤任务数量
tasktime,处理第个任务所需要的时间
responsetime,系统允许任务最⼤的响应时间,⽐如每个任务的响应时间不得超过2秒。参数设置
corePoolSize:
每个任务需要tasktime秒处理,则每个线程每钞可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:
tasks/(1/tasktime),即tasks*tasktime个线程数。假设系统每秒任务数为100~1000,每个任务耗时0.1秒,则需要100*0.1⾄
1000*0.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个线程。
allowCoreThreadTimeout:
默认情况下核⼼线程不会退出,可通过将该参数设置为true,让核⼼线程也退出。
以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,⽐如,当线程数量达到50时,CPU达到100%,则将maxPoolSize 设置为60也不合适,此时若系统负载长时间维持在每秒1000个任务,则超出线程池处理能⼒,应设法降低每个任务的处理时间(tasktime)。
平常我们经常都会使⽤到线程池,但是有没考虑过为什么需要使⽤线程池呢?下⾯我列举⼀下问题,⼤家可以思考⼀下
1.当前服务器的硬件环境是多少核的CPU,它和线程的关系⼜是什么?
2.jvm能创建多少个线程?
3.多线程主要解决什么问题?
4.你使⽤线程池的⽬的是什么?
以上⼏个问题都是帮助你更好的使⽤java的线程(还可以衍⽣更多的⼩问题,如:jvm维护线程的消耗,cpu调度线程的消耗,应该使⽤多少个线程才能最⼤化利⽤多核CPU..)。答案需要⾃⼰去百度,我这也讲不好,反⽽误导⼤家。
线程池顾名思义就是,存储了N多线程的⼀个池⼦,该池⼦维护了线程的创建、销毁,线程的创建数量,任务调度..
通常我们使⽤线程池,都是通过Executors ⼯⼚⽅法来创建⼀个线程池。该⼯⼚⽅法主要提供了以下⼏⼤类型线程池的创建:
1.CacheThreadPool
这是⼀个线程数变动性⾮常强的线程池,默认配置下,它可以开启⽆限多个线程(Integer.maxSize 和 JVM允许线程数范围内)。且如果该线程池⾥的线程在60秒内如果是处于空闲状态(即没任务执⾏),那么该线程就会被回收,不再由线程池维护。如果有新任务进来时,由于之前的线程池⾥的线程已被回收,那么新的线程也会再次创建。当执⾏完任务,60秒内依旧⽆新任务的可执⾏话,那么该线程⼜会被再次回收。
综合该线程池的特性,我们可以思考下什么情况下应该使⽤这类线程池。⽐如:我们的应⽤服务器上⾯,会在⾮固定时间(时间跨域度会尽可能⼤)和⾮固定的任务数量。
2.FixedThreadPool
FixedThreadPool⼀个固定数量的线程池,且该线程池不会随着任务的变化⽽增多或减少线程数量。即该线程池下的线程池如果你不主动调⽤销毁shutdowm、purge之类的⽅法。那么这些线程将会永远被线程池维护着。
3.SingleThreadExecutor
SingleThreadExecutor是⼀个固定单线程的线程池,该线程池会永远都保持着⼀个线程的活动状态,
如果该线程池的单线程因某些异常⽽退出后,线程池会继续创建⼀个新的线程。
4.ScheduledThreadPool
ScheduledThreadPool是⼀个⽀持任务定时调度的线程池。
以上四种线程池,除了ScheduledThreadPool,其他三种都是通过创建⼀个ThreadPoolExecutor对象来实现的,只是在构造该对象时候,初始化该内部的成员变量值不同,所以造就了其他三种类型的线程池的存在。下⾯看看ThreadPoolExecutor都有些什么成员变量,他们⼜分别有什么意义?
以下是ThreadPoolExecutor的构造⽅法之⼀,⽽Executors都是通过该构造⽅法来创建其他三种不同的线程池的。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
/**
在说这些参数之前,需要先了解下线程池内部的⼀些⼯作机制。
线程池ThreadPoolExecutor会⾃动调节线程池的⼤⼩(看PoolSize),依据corePoolSize参数的范围⼤⼩和maximumPoolSize。
当⼀个新任务通过ute或者submit被提交时,
如果当前线程池中的线程数量⼩于所设定的corePoolSize,⼀个新的线程则会被创建来处理该请求,即使这些核⼼的线程池是空闲的(当前没有处理任何任务如果当前线程池中的线程数量等于所设定的c
orePoolSize,那么新提交的任务则不会再创建新的线程,⽽是把该任务放进线程池所维护的任务队列workQueu 只有当前线程池中的数量超过了corePoolSize但是⼩于maximumPoolsize,且当queue满了才会去创建新线程(该创建的新线程必须⼩于maximumPoolsize)
以下是该构造⽅法的参数说明:
corePoolSize : 线程池的核⼼线程数。该参数并不是初始化时候写死了,线程池对象构造完成以后也能通过它提供的⼀些⽅法动态修改,ThreadPoolExecutor.
maximumPoolSize :线程池的最⼤线程数,只有当任务队列满了才会创建新的线程。该参数并不是初始化时候写死了,线程池对象构造完成以后也能通过它提
keepAliveTime :线程池所维护的线程的活动时间。如果超过了该时间范围,⽽线程还是空闲的,那么该线程将会被回收。不再由线程池所维护。以下是官⽅如果当前线程池数⼤于核⼼线程池数,且这些线程是空闲的超过所设定的存活时间keepAliveTime,那么这些多出来的线程则会被终⽌,可以降低线程资源消耗
unit :和上⾯keepAliveTime所对应,表⽰活动时间的时间单位。如毫秒、秒、分..
workQueue :初始化⼀组任务,但是该组任务并不会马上执⾏,需要⼿动调⽤prestartCoreThread或
者prestartAllCoreThreads来预先启动线程才会执⾏这些
threadFactory : 传⼊⼀个线程⼯⼚。通过该线程⼯⼚可以创建线程。它创建的所有线程都是通过同样的ThreadGroup和同样的NORM_PRIORITY和non-daem 如果我们⾃⼰重写封装了⼀遍线程⼯⼚,还有个好处就是可以通过该线程⼯⼚实例维护所有由它创建的线程。
上次我在⾥看到⼀个友说,他去⾯试,⾯试官问他如何判断线程池中⼀个指定线程当前是否在执⾏任务,你们觉得可以吗?
java线程池创建的四种**/
场景:重新初始化ThreadLocal。收集统计信息,添加⽇志记录。。
Queue maintenance
上⾯说明了线程池的⼀些构造参数和外部⽅法,了解了这些后我们可以直接使⽤ThreadPoolExecutor来构建适合我们的线程池,⽽不再需
要Executors。当然,通过Execturos创建的线程池再配合上⾯说的⼀些动态设置参数的⽅法,也能起到⼀些很好的效果。
准备开始解刨核⼼源码了!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论