PPXu

Java线程池类比公司经营之道

2018-10-03

  Java线程池的设计与公司经营的相似之处如果我们查看JDK源码,会发现FixedThreadPool、CachedThreadPool和SingleThreadExecutor都是通过创建一个ThreadPoolExcutor对象来实现的。我们来看一下该ThreadPoolExcutor的构造方法,并对线程池中线程的保留和新建策略做进一步的分析。

1
public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)

  第一个参数corePoolSize代表了线程池中一定要保持的线程的数量;线程池中的线程可能发生变化,第二个参数maximumPoolSize约束了线程池中所能达到的线程的最大数量;线程有可能一直处于空闲状态,keepAliveTime代表了空闲状态的线程所能存活的时间;TimeUnit代表了时间单位;workQueue是一个缓冲队列,如果任务到达,但是还没有空闲线程可以执行该任务,那么就将该任务置于这个缓冲队列中。为了更加容易理解和记忆线程池这个几个属性的协调工作。我们利用一个精明的老板来比喻线程池。而将线程比作线程中的线程。

  一个公司必须要保留一定数量的核心员工,不管这些员工是不是老闲着。当然,对于非常抠门的老板,这个数量可能是0,例如CachedThreadPool。核心员工的数量,就是corePoolSize。当一个公司初创时,所有的员工也就是那几个核心员工。当线程池新建时,同样只会创建与corePoolSize数量相当的线程。

  当新的任务到达时,如果有空闲线程,马上将这些任务分配给空闲线程。如果没有的话,那么,怎么办呢?新建一个线程吗?非也,对于一个精明的老板来说,他只会把这些任务排进任务列表。手下的员工忙完手头的工作,马上就从任务列表的开头位置移出工作,并分配给空闲。这就让每名员工都不停的工作,甚至加班加点。这个任务列表就是workQueue。

  如果更多的任务涌过来,如同这个公司的业务很好,工作多越堆越多。这个时候,就看任务列表能承受的极限了。有的老板在创立公司的时候,就抱着这种心态——任务列表可以无限长,反正我就招这么多人,客户能等就等,不能等就拉倒。但是,对于很多客户来说,如果等的时间过长,可能就放弃了。具有无限长workQueue的线程池来说,可能同样会导致某些线程等待时间过长,用户任务无响应的问题。

  但是,如果workQueue不是无限长,那么,其容量总有可能被达到。而新的任务到达时,无法存入workQueue。这如同,这个老板既负责任(不想出现客户无限等待的情况),同时又不想放弃任何一个客户。那么,唯有增加员工数量了,这就如同线程池新建线程。但是,公司总要有个风险评估,不能让员工数量无限增长,于是,maximumPoolSize就代表了员工的最大数量。如同说,在无法两全其美的情形下,即使损失部分客户,也要控制公司的成本风险。线程池同样如此,每个线程都将消耗系统资源,这种消耗必须被控制在一定范围之内。

  在大量任务涌入,workQueue无法缓存这些任务,而maxinumPoolSize也已经达到时,相当于一个公司达到了它的最大营运能力,就只能拒绝介绍客户任务了。线程池拒绝介绍新的任务,会抛出异常RejectedExecutionException。

  当然,一个公司的营运既有旺季,也有淡季。上面我们所描述的情形是旺季的营运。如果淡季到了,许多员工都闲下来了。老板就会考虑裁员了。当然,老板不会马上动手,因为不能准确把握旺季和淡季的分界线。他会给空闲员工一个缓冲期,如果这个员工闲了三个月都没工作,那么证明,真的需要裁掉他了。对应到线程池中,keepAliveTime和TimeUnit限制了一个线程的最大空闲时间。相当于一个缓冲期,缓冲期一结束,就会将其销毁,以释放系统资源。当然,这些被“处理”的线程都是核心员工数量之外的,线程池总会保留corePoolSize个线程备用。

  通过以上描述,我们应该对线程池的运作策略有了一个比较清晰的认识。总结这种策略,主要目的是基于成本考虑——尽量耗用最少的内存,来完成尽可能多的任务。

扫描二维码,分享此文章