ThreadPoolExecutor线程池是并发编程中用的比较多的一个类,项目和面试的时候经常会用到,所以了解一下是很有必要的。
线程池是池化技术的一种。它有以下优势:
ThreadPoolExecutor的最多参数的构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
ThreadPoolExecutor的创建主要参数有7个,接下来将进行一一介绍。
默认情况下,当一个任务请求时,核心线程数才会被创建和启动,但是也可以通过prestartCoreThread启动一个核心线程或者prestartAllCoreThread启动所有核心线程。
ThreadFactory用来创建线程。如果没有指定ThreadFactory的话,默认会使用Executors#defaultThreadFactory 来创建线程,并且这些线程都是在同一个ThreadGroup并且都是非守护线程状态(non-daemon status)并且拥有相同的优先级(NORM_PRIORITY)。如果指定了ThreadFactory 可以修改ThreadGroup和线程的名称、守护状态、优先级等。ThreadFactory如果调用newThread(Runnable r)方法返回null则创建线程失败,线程池会继续运行但可能不会执行任何任务。线程应该拥有"modifyThread"权限,如果工作线程或者其他线程没有拥有这个权限,服务可能会降级,配置更改可能不会及时生效,关闭线程池可能保持在可能终止但未完成的状态。
存活时间(Keep-alive times):空闲线程等待工作的超时时间(以纳秒为单位) 如果当前线程池中的线程数超过了核心线程数,超出的部分线程如果空闲的时长大于存活时长,那么他们将会被终止运行。当线程池不被频繁使用的时候,这提供了一种减少资源消耗的方法。存活时间可以通过setKeepAliveTime(long, TimeUnit)进行修改,使用 setKeepAliveTime(Long.MAX_VALUE, NANOSECONDS)有效地禁止空闲线程在关闭之前终止。默认情况下,存活策略只适用于当前线程数超过核心线程数的情况下。但是使用方法allowCoreThreadTimeOut(boolean)也可以将这个超时策略应用到核心线程,只要keepAliveTime值不为零。
TimeUnit 是存活时间的单位。
任何实现了BlockingQueue接口的实现类都可以用来传输和保存提交的任务,阻塞队列的使用和线程池大小相关:
在调用execute(Runnable)提交任务时,在Executor已经关闭或者有界队列的最大线程数和队列满的情况下任务会被拒绝。不论在什么情况下,execute方法调用RejectedExecutionHandler#rejectedExecution(Runnable, ThreadPoolExecutor)任务都会根据拒绝策略被拒绝。
ThreadPoolExecutor预定义了四种拒绝策略:
也可以自己实现RejectedExecutionHandler接口,并重写rejectedExecution方法来自定义拒绝策略。
关于上面的参数我试着通俗的说一下,希望我说的能让你明白。
假如现在有一家外包公司(ThreadPoolExecutor),公司的核心开发(corePoolSize)有5个人,公司最多容纳(maximumPoolSize)10个开发,现在公司接了一个项目,核心开发还忙的过来,就将这个项目给其中一个核心开发做,慢慢的销售人员接的项目越来越多,5个核心开发都在做项目没时间再做新的项目,公司为了节省开支新来的项目只能先接过来暂时积压(BlockingQueue)起来,但是一直积压也不是个事情,客户也会一直催,公司顶住最多只能积压5个,积压到5个之后公司也还能容纳5个开发,不得不再招人处理新的项目。当公司发展的越来越好,接的项目也越来越多这10个开发也忙不过来了,有新的项目再进来就只能通过各种方式拒绝(RejectedExecutionHandler)了。再后来因为疫情原因,公司能接到的项目也越来越少了,开发人员很多(Thread)已经没事儿可做了,大概过了两周时间(keepAliveTime),公司又为了节省开支就把这些空闲下来的非核心开发给开了。当然,核心开发也不是说一定不能动也是可以开的(allowCoreThreadTimeOut(true)),只不过肯定是优先考虑非核心人员。有人说了,项目多的时候为啥不扩大公司规模呢?首先,公司老板最多也就有养这几个员工的的能力,养的多了老板也吃不消,多招一个人可能也不会使工作效率提高,反而可能拖累其他开发的进度,能养几个员工也是经过老板深思熟虑加以往的经验总结得出的结果。
线程池大致流程图
ThreadPoolExecutor提供了protected权限的beforeExecute(Thread, Runnable)和afterExecute(Runnable, Throwable)方法供子类重写,这两个方法可以在任务执行的前后调用。这些可以用来操作执行环境,例如:重新初始化ThreadLocal、收集统计信息或者添加日志,类似静态代理。另外terminated方法也可以被重写用来处理特殊情况,当Executor完全被终止时。如果钩子方法或者回调方法抛异常,工作线程可能会执行失败或者突然终止。
可以通过方法getQueue获取工作队列并进行监控和调试,如果是为了其他目的则强烈反对这么做。当大量的任务被取消时,方法remove(Runnable)和purge可用于储存回收。
线程池如果在系统中没有再被引用并且没有线程在使用时将会被自动关闭,如果你想确保未被使用的线程池被回收即使用户忘记调用shutdown方法,你必须通过设置合适的存活时间、使用零核心线程的下限或者设置#allowCoreThreadTimeOut(boolean)来使未被使用的线程最终关闭。
能力一般,水平有限,如有错误,请多指出。