ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
线程池维护的核心线程数。
为什么这里说核心线程数而不是最小线程数是因为在线程池被创建后,并不会直接创建corePoolSize个线程,而是等任务到来时临时创建。等按照需要创建了corePoolSize个线程之后,这些数量的线程即使闲置,也不会被线程池收回。这时即可以将这个值理解为线程池维护的最小线程数了。
maximumPoolSize
线程池维护的最大线程数。
keepAliveTime
当线程池中的线程数量大于corePoolSize,多出那部分数量的线程空闲keepAliveTime后会被收回。
uni
keepAliveTime的时间单位。可选的参数为java.util.concurrent.TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。
workQueue
存放通过execute方法提交给线程等待执行的任务的队列。
threadFactory
负责给线程池创建线程的工厂。
handler
在当队列达到极限导致任务执行阻塞时执行的处理策略。handler有四个选择:ThreadPoolExecutor.AbortPolicy(抛出java.util.concurrent.RejectedExecutionException异常)。ThreadPoolExecutor.CallerRunsPolicy(重试添加当前的任务,他会自动重复调用execute方法)。ThreadPoolExecutor.DiscardOldestPolicy(抛弃旧的任务)。
ThreadPoolExecutor.DiscardPolicy(抛弃当前的任务)。
对于一般的应用,我们不用通过构造函数来创建线程池,而是用一些封装过的工具方法,这些方法设置了大多数参数的缺省值。只有对线程池的特性有更高的要求时,才直接使用构造函数。
Executors.newCachedThreadPool(创建自动增加或减少容量的线程池)
Executors.newFixedThreadPool(创建固定容量的线程池)
Executors.newSingleThreadExecutor(单独线程的线程池)。
下面是一些高级特性
corePoolSize和maximumPoolSize
ThreadPoolExecutor会根据corePoolSize和maximumPoolSize的值调整线程池中线程的数量。当通过ThreadPoolExecutor.execute方法向线程池提交一个新的任务时,如果线程池当前线程数量小于corePoolSize,就算有线程空闲,也会在创建一个线程执行这个任务;如果线程池当前线程数量大于corePoolSize又小于maximumPoolSize,只有当可用线程不够的时候才会创建新的线程。如果不希望系统动态增减线程数量,则将corePoolSize和maximumPoolSize数值设置为一样的值。如果将maximumPoolSize设置为一个特别大的值如Integer.MAX_VALUE,则ThreadPoolExecutor成为了一个能够容纳大量并发任务的线程池。一般来说corePoolSize和maximumPoolSize是在构造ThreadPoolExecutor对象时设置好的,当仍然可以调用ThreadPoolExecutor.setCorePoolSize 和ThreadPoolExecutor.setMaximumPoolSize 方法修改这两个属性。
关于线程创建
线程池中的线程是由ThreadFactory创建。如果不特别指定,会使用Executors.defaultThreadFactory创建位于同一个线程组,相同优先级(NORM_PRIORITY)的非守护线程。如果由你来指定ThreadFactory,你可以定制线程名字,线程组,优先级,是否为守护线程等属性。
直到需要时才开始创建线程
缺省情况下,在线程池刚刚创建好之后其中是没有任何线程存在的。直到向线程池中提交了任务。可以通过复写ThreadPoolExecutor.perstartCoreThread或ThreadPoolExecutor.prestartAllCoreThreads方法改变默认行为。
线程的存活时间
如果线程池中当前线程数大于corePoolSize,如果超过keepAliveTime后仍然没有使用,则超出部分会被终止掉。当需要更多线程的时候,再重新创建。这个属性也可以通过setKeepAliveTime修改。
队列
BlockingQueue用来管理被提交进来等待执行的任务。是否进入queue和线程池的容量有关。如果线程池中运行的线程小于corePoolSize,新的任务不会进入queue而是在新建的线程中执行。如果线程池中运行的线程大于或等于corePoolSize,更倾向于将任务加入queue而不是新建线程。如果queue满了,则创建新的线程执行任务。如果线程数量大雨了maximumPoolSize,任务被拒绝。
以下是三种列队的策略:
1.直接传递:这也是缺省的实现使用SynchronousQueue,直接将队列中的任务转交给线程。如果将任务提交给队列时没有足够的线程处理这个任务,新的线程会被创建。一般来说需要将maximumPoolSize设置为最大以避免出现拒绝新提交来的任务的情况出现。当然了如果任务提交的速度大过了处理任务的速度会引起线程池中线程无限增长的问题。
2.无限队列:使用不限制容量的队列LinkedBlockingQueue。当所有corePoolSize的线程都在忙碌时新提交进来的任务在队列中等待。也就是说,即使线程池中的线程数小于maximumPoolSize,也不会有新的线程被创建(maximumPoolSize参数失效)。这种策略适用于提交来的各个任务相互独立的场景。例如,一个网页服务来说,使用这种策略能平滑瞬间突发的访问请求。
3.有限队列:使用有限队列防止将maximumPoolSize设置为最大时,资源耗尽的问题。调整队列大小和maximumPoolSize之间关系比较就变得重要了。使用大容量队列和较小的线程数可以降低CPU和资源的使用但会导致效率低下。小容量的队列需要相对较大的maximumPoolSize配合,增加了CPU调度线程的负担。
· 任务拒绝
调用ThreadPoolExecutor.execute方法提交新任务时,如果线程池已经被停止运行或线程数量、队列数量已满,RejectedExecutionHandler.rejectedExecution被调用。
=============================================================================================
上面说的都有点抽象,确实得在使用的时候细细研究才成。不过如果想做到遇到问题将其信手拈来,还是有必要记住这个线程池处理的基本逻辑的。下面就举个现实生活中的例子帮助理解一下。
我们可以把一个线程池(ThreadPoolExecutor)想成公司的一个部门
A(Executors.newCachedThreadPool):这个部门负责处理从客户那里分配过来的任务(Task)。在部门刚刚建立之初人力资源部设定了部门人员的上限(maximumPoolSize=60)和下限(corePoolSize=0),并要求部门经理等到第一个任务到来的时候才可以招收第一个组员(直到需要时才创建线程)。部门还制定了一个自己的工作制度,那就是我们绝不能够让任务积压!部门一成立,任务便从客户那边分配过来,部门经理为每一个任务招收一名员工。当员工完成手头的工作,部门经理为他们放假一段时间(keepAliveTime)进行休息。经过休整回来的员工,精神满满的等待接受经理给你派发的任务继续快乐的工作。可是这段时间经济不景气,没有那么多工作需要完成,处于节约成本的考虑,公司要把没有活干的非核心员工(由于corePoolSize=0,所以部门没有核心员工)解雇。到了年底,工作越来越多,部门经理继续招人,直到部门人数达到了上限,每一个员工都在拼命地工作没有一丝喘息的机会。这时又一个任务分配给了这个命苦的部门,也就是这个任务成为了击垮部门的最后一根稻草。部门经理受不了了,决定抗议:我们干不了更多的工作了(AbortPolicy)!
B(Executors.newFixedThreadPool):这个部门负责处理从客户那里分配过来的任务(Task)。在部门刚刚建立之初人力资源部设定了部门人员的下限(corePoolSize)。并要求部门经理等到第一个任务到来的时候才可以招收第一个组员(直到需要时才创建线程)。部门还制定了一个自己的工作制度,那就是我们绝不能够让任务积压的太多(LinkedBlockingQueue.capacity)!部门一成立,任务便从客户那边分配过来,部门经理为每一个任务招收一名员工。一直等到部门人数达到部门人员的下限就不能再招人了。多余的任务就要搁置在任务积压列表中,等其他任务完成后再逐个完成。由于这个部门都是核心员工,所以大不用担心被解雇的问题了。当分配给部门的任务打过了积压任务的上限时,部门经理调出来说:我们干不了更多的工作了(AbortPolicy)!这个部门的工作人员下限可以设置,极端情况下部门只有一个员工(Executors.newSingleThreadExecutor)。
后来公司觉得,如果派发给部门的任务过多时,部门经理仅仅跳出来喊一声拒绝有点太过简单,还应该允许他做更多的事情化解危机。于是部门经理逐渐学会了ThreadPoolExecutor.CallerRunsPolicy(重试添加当前的任务,他会自动重复调用execute方法)。ThreadPoolExecutor.DiscardOldestPolicy(抛弃旧的任务)。 ThreadPoolExecutor.DiscardPolicy(抛弃当前的任务)。
由此可见,ThreadPoolExecutor线程池,兼顾了使用的方便性和扩展的灵活性。对于一般用途,借助Executors足以。若是将ThreadFactory ,RejectedExecutionHandler,BlockingQueue的不同实现灵活组合,又能满足各种情况下的需求真不枉为大家之作。
================================================================================================
顺便介绍一下,多线程大师Doug Lea。也就是java.util.concurrent包的作者。
如果IT的历史,是以人为主体串接起来的话,那么肯定少不了Doug Lea。这个鼻梁挂着眼镜,留着德王威廉二世的胡子,脸上永远挂着谦逊腼腆笑容,服务于纽约州立大学Oswego分校计算器科学系的老大爷。
说他是这个世界上对Java影响力最大的个人,一点也不为过。因为两次Java历史上的大变革,他都间接或直接的扮演了举足轻重的脚色。一次是由JDK 1.1到JDK 1.2,JDK1.2很重要的一项新创举就是Collections,其Collection的概念可以说承袭自Doug Lea于1995年发布的第一个被广泛应用的collections;一次是2004年所推出的Tiger。Tiger广纳了15项JSRs(Java Specification Requests)的语法及标准,其中一项便是JSR-166。JSR-166是来自于Doug编写的util.concurrent包。
值得一提的是: Doug Lea也是JCP (Java小区项目)中的一员。
Doug是一个无私的人,他深知分享知识和分享苹果是不一样的,苹果会越分越少,而自己的知识并不会因为给了别人就减少了,知识的分享更能激荡出不一样的火花。《Effective JAVA》这本Java经典之作的作者Joshua Blosh便在书中特别感谢Doug是此书中许多构想的共鸣板,感谢Doug大方分享丰富而又宝贵的知识。