java 中线程池的核心类是j.u.c包下的ThreadPoolExecuter,继承自AbstractExecuterService。ThreadPoolExecuter提供了四个构造函数,但是实际上其中三个构造函数最终是调用的第四个构造函数实例化线程池。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
从上面的四个ThreadPoolExecuter的构造函数,我们可以看到若干个可以对线程池进行设置的参数,下面对每个参数做一下简要的介绍:
核心池大小,指的是正常情况下线程池中线程额数量。这里的正常情况不包括线程池刚启动时、或者长时间无任务提交到线程池、或者大量的任务提交到线程池的情况。值得一提的是,线程池刚建立时,池中是没有线程的,直到任务来临,或者调用preStartCoreThread()或者preStartAllCoreThread()强制创建核心线程。(如果这里不理解的话,可以看完全文再回来看看这段话。)对于计算密集型任务,在拥有NCPU个处理器的系统上,当线程池的大小为NCPU+1时,通常能实现最优的利用率。
线程池最大线程数。
线程空闲时,最大的存活时间。默认情况下,只有线程数量大于核心池数量时,该参数才会起作用,直到剩余线程数不大于核心池大小。
keepAliveTime的单位。
阻塞队列,用于存储等待执行的任务。
线程工厂,一般使用默认的即可,Executors.defaultThreadFactory()。
饱和策略,用于拒绝任务。主要有:
1、ThreadPoolExecutor.AbortPolicy:中止策略,默认的饱和策略,将丢弃任务并抛出未检查的RejectedExecutionException。
2、ThreadPoolExecutor.DiscardPolicy:抛弃策略,悄悄抛弃该任务,且不抛出异常。
3、ThreadPoolExecutor.DiscardOldestPolicy:抛弃最久的策略,将抛弃最旧的任务(即任务队列中的第一个任务,也是下一个将要被执行的任务),然后重新尝试提交新任务到线程池中,重复这一过程,直到提交成功。注意这一策略最好不要和优先队列一起使用,否则将会抛弃优先级最高的任务。
4、ThreadPoolExecutor.CallerRunsPolicy:调用者运行策略,实现一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将任务退回给调用者,调用者在执行该任务的这段时间内,将不会继续向线程池提交任务,从而降低递交到线程池的任务流量。
要想充分了解任务提交的过程,可以阅读 ThreadPoolExecutor的源码,期间可以重点理解一下Worker与Work Queue。下面是一个简要的流程:
1、任务提交给线程池,如果当前线程池的线程数小于核心池的大小,则创建线程去执行该任务。
2、否则尝试将该任务放入workQueue中,若添加成功,则等待Worker去将其取出执行。
3、否则检查当前线程数是否小于maximumPoolSize,如果小于,则创建新的线程去执行该任务。
4、否则根据饱和策略进行处理。
注意,在使用线程池的过程中,只有1、3两种情况下才会去创建新的线程(这里不包含preStartCoreThread()或preStartAllCoreThread())。
任务提交到线程池的过程中,可以发现核心池是正常情况下线程池中的线程数量,即worker的数量。通俗的理解就是线程池相当于一个工厂,正常情况下,工厂中的工人(worker)的数量是固定的,足以应付一般情况下的订单,即使部分订单不会马上处理(work queue)。但是当订单的高峰期来临时,订单积压过多,这个时候需要请临时工(大于核心池的线程)来处理订单;当订单数量超过工厂的承受能力的时候,就要拒绝新的订单了;当订单的高峰期过去之后,工人空闲下来了,就要逐步的辞退临时工人(keepAliveTime),甚至正式工人(allowCoreThreadTimeOut()),从而避免资源的浪费。
类库中提供了一个灵活的线程池ThreadPoolExecutor的同时,也提供了一些有用的默认配置,一般情况下我们可以采用默认的线程池配置。通过调用Executors的静态工厂方法之一来创建一个默认配置的线程池:
1、newFixedThreadPool(int):创建指定长度的线程池,核心池等于最大线程数。
2、newSingleThreadPool():创建长度为1的线程池,相当于newFixedThreadPool(1).
3、newCachedThreadPool():将创建一个可缓存的线程池,此时线程池规模不受限制(Integer.MAX_VALUE),核心池大小为0.
4、newScheduledThreadPool:创建一个固定长度的线程池,以延迟或定时的方式执行任务。
参考:
[1]《Java并发编程实战》Brian Geotz et al.
[2]《OpenJDK 8u》