Java线程池之构造器创建
1、构造函数的参数介绍
上一篇文章中我介绍了通过Executors类快速创建不同类型的线程池的方法,后来发现其内部调用的都是ThreadPoolExecutor的构造器方法,通过传入不同的参数可以构造出不同的线程池。下面我介绍一下这7个参数。(内容翻译自ThreadPoolExecutor.class)
方法签名:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数解释:
corePoolSize:即使它们处于空闲状态也会保留在线程池中的线程数量。
maximumPoolSize:线程池中的最大线程数量。
keepAliveTime:当线程数大于corePoolSize时,这是多余的空闲线程在终止之前等待新任务的最长时间。
unit:时间单位。有NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS、MINUTES、HOURS、DAYS。
workQueue:在任务被执行前用来保留任务的队列。改队列值保留通过execute方法提交的任务。
threadFactory:执行器创建新线程时使用的工厂。
handler:因达到线程界限和队列容量而阻止执行的处理器。目前有四种:AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy。
2、corePoolSize
ThreadPoolExecutor会根据corePoolSize和maximumPoolSize设置的边界值自动调整线程池的大小。
规则如下:
1、当一个新任务在execute(Runnable)中提交,并且此时线程池中运行的线程数少于corePoolSize,此时就会创建一个新线程,及时有其他线程是空闲状态。
2、如果corePoolSize < 运行的线程数 < maximumPoolSize,只有当队列workQueue已满时才会创建新的线程。也就是说,线程的提交顺序是:corePool < workQueue < maximumPool。
3、如果corePoolSize=maximumPoolSize,则说明此线程池是一个固定大小的线程池。
4、如果将maximumPoolSize设置为一个本质上不受限的值(例如Integer.MAX_VALUE),则说明允许线程池容纳任意数量的并发任务。
3、threadFactory
线程池中的线程的来源就是threadFactory。ThreadFactory是一个接口,内部只有一个方法:
Thread newThread(Runnable r);
对于线程池的构造来说,要是没有额外指定的话,将使用Executors.defaultThreadFactory()。该工厂默认创建的线程全部位于同一个ThreadGroup中,并且具有相同的优先级NORM_PRIORITY。NORM_PRIORITY是Thread类中的一个静态常量,常见的是三个:MIN_PRIORITY、NORM_PRIORITY、MAX_PRIORITY。
通过提供不同的ThreadFactory,可以更改线程的名称name、线程组group、优先级priority、是否守护线程daemon等属性。
4、keepAliveTime
当线程池中的线程数超过corePoolSize时,多余的线程将会在超过keepAliveTime时终止。可以使用setKeepAliveTime(long,TimeUnit)方法动态更改此参数。默认情况下,只有当存在的线程多于corePoolSize个时,keep-alive的策略才生效。当keepAliveTime的值不为0的时候,也可以通过方法allowCoreThreadTimeOut(boolean)将此策略应用在线程数少于corePoolSize个的时候。
5、workQueue
BlockingQueue是一个接口,目前现有的实现有:ArrayBlockingQueue、BlockingDeque、DelayQueue、LinkedBlockingQueue、LinkedBlockingDeque、LinkedTransferQueue、PriorityBlockingQueue、SynchronousQueue等。每一种实现都是基于不同的场景进行设计出来的。
在线程池中,任何BlockingQueue都可以用来传输和保留提交过的任务。
队列的一般规则有:
1、如果正在运行的线程数小于corePoolSize,则执行器倾向于添加新线程。
2、如果正在运行的线程数大于corePoolSize,则执行器倾向于将新任务放入队列中进行排队,而不是添加新线程。
3、如果无法将新任务放入队列中,则会创建一个新线程。
4、如果总线程数超过了maximumPoolSize,新任务就会被拒绝。
常见的排队策略
1、Direct handoffs
工作队列中一个比较好的默认选择是SynchronousQueue,它直接将任务交给线程,而不是保留它们。如果没有立即可用的线程来运行任务的话,将任务排队的尝试会失败,也就是说没法将任务进行排队,而只能通过构建新线程的方式处理任务。在处理具有内部依赖的很多请求时,此策略避免了锁定lockups。此策略需要一个无大小限制的maximumPoolSize,用来避免对于新任务的拒绝提交。也就是说,当maximumPoolSize是一个有界的数值时,一旦线程数不够用的时候新任务就会被立即拒绝。这种策略下,当任务被处理的平均时长大于任务的到达速度,线程数就有可能无限增长。
2、Unbounded queues
使用无边界的队列(例如,没有预定义容量的LinkedBlockingQueue),在所有corePoolSize个线程忙碌的时候会将新任务添加到队列中。使用此策略情况下,线程数最多为corePoolSize,因为多余的任务已经被加入到队列中,因此设置maximumPoolSize的值已经没有意义了。当每个任务都独立于其他任务的时候,这种策略比较合适,因为任务之间不会影响彼此的执行。这种队列在处理短暂的突发请求的时候比较有用,但是当任务被处理的时间大于任务到达的速度时,工作队列的长度依然会无限增长。
3、Bounded queues
使用有界队列(例如,ArrayBlockingQueue),将maximumPoolSize设置为一个有限值,可以避免资源被耗尽。
此策略在调优和控制方面比较困难。队列大小和线程池的大小需要互相折中。
假如使用大队列和小池,则CPU的使用率、OS的资源、上下文切换的开销会得到优化,但是会导致吞吐量降低。
假如使用小队列和大池,则CPU会非常繁忙,但是调度资源的开销也会增大,吞吐量也有可能降低。
6、handler
拒绝策略的出现场景:执行器Executor关闭、执行器使用有界的线程池、执行器使用有界的队列。
无论是哪种情况,execute(Runnable)方法都会调用其RejectedExecutionHandler的 RejectedExecutionHandler.rejectedExecution(Runnable,ThreadPoolExecutor) 方法。
常见的拒绝策略:
ThreadPoolExecutor.AbortPolicy
此策略是默认的拒绝策略。处理器handler一旦拒绝,就会抛出运行时异常RejectedExecutionException。
ThreadPoolExecutor.CallerRunsPolicy
在调用者进程中执行被丢弃的任务。这提供了一种简单的反馈机制,任务提交线程的性能将会显著下降,这将降低新任务的提交速度。
ThreadPoolExecutor.DiscardPolicy
简单地删除无法执行的任务。
ThreadPoolExecutor.DiscardOldestPolicy
如果执行器还未关闭,则工作队列队首的任务将会被删除。然后尝试再次提交当前任务。再次尝试也可能失败,然后又会再次尝试。
7、常见线程池的底层实现
7.1 newFixedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特性
1、corePoolSize = maximumPoolSize = nThreads
2、空闲线程的存活时间是0
3、工作队列是无界队列
7.2 newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特性
1、核心线程数为0
2、线程数最大可达Integer.MAX_VALUE
3、队列不存储任务
4、空闲线程默认的存活时间是60秒
7.3 newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
特性
1、核心线程数和最大线程数都是1
2、工作队列是无界队列