概述
先看一下ThreadPoolExecutor的继承关系:
线程池ThreadPoolExecutor主要用来解决两个问题:
- 通过减少每个任务的调度开销,从而提高大量异步任务执行效率
- 提供了一种管理和限制资源(如线程)的方法
此外线程池也提供给了一些基本的统计信息,如完成的任务数等。线程池有以下好处:
- 降低资源消耗:通过重用已经创建的线程来降低线程创建和销毁的消耗
- 提高响应速度:任务到达时不需要等待线程创建就可以立即执行。
- 提高线程的可管理性:线程池可以统一管理、分配、调优和监控。
ThreadPoolExecutor提供了很多自适应的参数和可扩展的hooks,但JUC包也停工了更为简洁的类Executors
,Executors
提供了newFixedThreadPool(int nThreads)
固定线程池,newSingleThreadExecutor()
单线程线程池,newCachedThreadPool()
无限大小线程池等工厂方法来创建各种常见的线程池。主意某些编码规范中不推荐使用Executors
,而是使用ThreadPoolExecutor
,因为这样可以加深对线程池原理的理解。
核心参数解读
Core and maximum pool sizes
线程池中的线程数目会根据corePoolSize
和maximumPoolSize
两个参数来自动调整。当一个新的任务经由execute
方法提交后,如果当前运行的线程数小于corePoolSize
,即使其它工作线程空闲,也会新创建一个线程来处理这个任务。如果当前运行线程在corePoolSize
和maximumPoolSize
之间,只有当队列(queue)满时,才会创建新的线程处理任务。通过设置相同的corePoolSize
和maximumPoolSize
,可以创建一个固定大小的线程池(与Executors.newFiexedThreadPool()
类似),通过设置maximumPoolSize
到一个无限制的值,如Integer.MAX_VALUE
,就可以创建一个容许任意数目并发任务的线程池。
通常,这两个参数在ThreadPoolExecutor
对象构建的时候,但也可以通过setCorePoolSize(int)
和setMaximumPoolSize(int)
方法动态修改。
On-demand construction
默认情况下,核心线程在线程池初始化的时候创建,在新的任务提交后启动。可以通过方法prestartCoreThread()
和prestartAllCoreThreads()
动态覆盖,比如如果使用非空队列构建线程池,就可能需要预先启动核心线程。
创建新线程
新线程通过ThreadFactory
创建,如果构建线程池的时候没有指定ThreadFactory
,默认使用Executors.defaultThreadFactory()
创建新线程,而且所有其创建的线程都在同一个ThreadGroup
下,优先级都为NORM_PRIORITY
,都是非守护线程。如果自己指定ThreadFactory
,就可以自定义线程组,线程名称,优先级,守护状态等参数。
Keep-alive times
如果线程池内的线程数大于corePoolSize
,则大于corePoolSize
的线程在空闲后的keepAliveTime
时间后,会自动终结,这样做是为了减小有空闲线程时资源消耗。这个参数也可以通过方法setKeepAliveTime(long, TimeUnit)
动态设置。通常,keepAliveTime
参数只对大于corePoolSize
的额外线程有效,但是如果设置了allowCoreThreadTimeOut(boolean)
,可以将这个超时时间也作应用于核心线程。
Queuing
任何阻塞队列BlockingQueue
都可以被线程池用来传输和暂存提交的任务。队列的使用与线程的大小相关:
- 如果工作线程数小于
corePoolSize
,执行程序通常会创建线程,而不是将新任务放进队列排队 - 如果工作线程大于等于
corePoolSize
,执行程序通常会将新线程放进队列排队,而不是创建新线程执行任务 - 如果新的任务请求无法排队,会创建新的线程只到工作线程达到
maximumPoolSize
,如果工作线程大于maximumPoolSize
,则这个任务提交请求将会被拒绝(详见拒绝策略)。
队列常见的有三种类型:
- 直接递交转发,即直接将任务传递给工作线程,不会暂存任务。
SynchronousQueue
队列就是这种队列。这种队列通常需要设置无限制的maximumPoolSize
,使得线程池不会拒绝新任务的提交请求。但是这样使得如果任务处理速度达不到提交速度时,线程池工作线程可能无限增长。 - 无限队列,如一个未预设容量的
LinkedBlockingQueue
,无限队列使得所有的新任务在核心线程满的时候进入队列排队。此时不会又超过corePoolSize
的线程被创建,所以这是的maximumPoolSize
的设置不会起作用。这种队列适用于所有的任务相互独立,并且任务的执行不会相互影响。这种队列可以平滑的消除瞬间大量并发的请求,但也会导致队列无限增长。 - 有限队列,如
ArrayBlockingQueue
。有限队列加上有限的maximumPoolSize
可以阻止资源耗尽,但是却难以调度和控制,大队列和小池可以减少cpu的使用、上下午的调度切换等,但是却降低了系统的吞吐量;当如果使用小队列和大池,可能会使得cpu一直忙碌,但是也增加了cpu切换的开销,也可能降低吞吐量。
Rejected tasks
如果执行程序关闭,或者有限池和有限队列满时,新提交的任务将会被拒绝。无论哪种情况,RejectedExecutionHandler
会调用RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)
方法来处理拒绝策略,ThreadPoolExecutor
内置了四种拒绝策略:
- 默认的拒绝策略是
ThreadPoolExecutor.AbortPolicy
,其拒绝方式是直接抛出运行时异常RejectedExecutionException
。 ThreadPoolExecutor.CallerRunsPolicy
,这种策略直接调用线程的execute
方法来运行这个任务,这种策略降低了线程池的提交速度,是一种简单的反馈控制机制。ThreadPoolExecutor.DiscardPolicy
策略,这种策略简单的丢弃这个新提交的任务。ThreadPoolExecutor.DiscardOldestPolicy
策略,这种策略适用于线程池满的情况,不适用于执行程序关闭的情况。其做法是丢弃队列头部的任务。
当然我们也可以使用自定义的RejectedExecutionHandler
类。需要注意的是有些策略仅仅在有限队列和有限池的情况下工作。
Hook methods
ThreadPoolExecutor
提供了protected
的beforeExecute(Thread, Runnable)
和afterExecute(Runnable, Throwable)
供自定义重载。这两个方法分别在任务执行前后执行,可以用来准备任务执行环境等。如初始化threadLocal
变量,统计或者添加日志等。
如果挂钩方法或者回调方法执行异常,则工作线程也会终止。
关键方法\属性\算法
AtomicInteger ctl
用来记录当前线程池的线程数workerCount
和线程池的状态runState
。其中低29位是workerCount
,高三位是runState
,其中线程池状态枚举值包括:
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
然后通过内部静态方法分别取出ctl中的workerCount
和runState
:
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
BlockingQueue<Runnable> workQueue
用来缓存和传送任务的队列,不需要通过workQueue.poll() == null
来判断队列是否为空,而是根据workQueue.isEmpty()
判断即可。
ReentrantLock mainLock
主锁,取得锁方可访问线程集合(workers set)。
HashSet<Worker> workers
线程集合,用来容纳池内所有线程。只有获得mainLock
才可以访问workers
。
构造方法
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
具体参数不再详解,参考关键属性
public void execute(Runnable command)
execute()
方法用于线程提交。具体实现:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
不难看出其处理流程:
- 当前工作线程数小于
corePoolSize
,新提交任务时,线程池会试图创建新的线程,并把这个任务作为其第一个任务。 - 否则将试图将当前线程入队列,如果入队列成功还需要重新检查进入本分支后,线程池是否shutdown,如果线程池shutdown,就从队列中移除当然任务,然后拒绝这个任务提交,否则在工作线程为0的情况下,创建新的线程(并没有将新的这个线程作为第一个任务)。
- 如果任务入队列失败,则尝试创建新的线程,创建失败则说明了当前线程池的状态变化或者线程池已满,接下来就会拒绝这个任务提交。
核心方法addWorker()
addWorker方法首先会检查线程池的运行状态,判断过程:
- 如果线程池状态rs为-1(即RUNNING),则先决条件判断通过
- 如果线程池状态rs>=SHUTDOWN,则进行后续判断
- 如果线程池状态rs>SHUTDOWN,即线程池处于STOP,TIDYING或者TERMINATED状态,直接返回false
- 如果线程池状态rs==SHUTDOWN,即线程池处于SHUTDOWN状态,进行后续判断,继续判断
- 如果此时fisrtTask不为空,即线程池处于SHUTDOWN状态,还有新的任务提交,则直接拒绝该任务提交
- 如果firstTask为空,判断阻塞队列为空,即线程池处于SHUTDOWN状态,而且阻塞队列为空,也没有新任务提交,无需创建线程,返回false,否则表示队列还有任务未执行,可以创建新任务,判断条件通过
如果上述先决条件判断通过,则判断工作线程数是否达到允许最大值((1 << 29) - 1
),或者工作线程大于corePoolSize(添加核心线程)或者最大线程maxPoolSize(添加非核心线程),直接返回false,新线程创建失败。线程池工作线程数判断条件通过,则尝试对线程数经常cas加一,如果工作线程加一操作成功,跳出循环,进行创建线程操作。否则判断当前线程池状态rs,如果此时rs和进入方法时的状态变化,则重复上述全部判读。
进入创建线程的流程,创建Worker的实例,用来容纳任务。然后获取mainlock的锁,获取锁后,进一步检查当前线程池的运行状态,如果线程池处于RUNNING状态或者线程池SHUTDOWN而且firstTask为空时,先检查当前任务是否可运行,如果可运行,将新创建的worker添加到工作线程集和workers
中,否则抛出异常。添加任务到workers
集合后,修改largestPoolSize
。如果上述一切顺利,启动线程执行任务:t.start();
,否则执行addWorkerFailed
,回滚工作线程数workCount。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//线程池状态判断通过,判断工作线程数
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
//workCount cas +1 success,break the loop and start to create new thread
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
//runStat changed since enter this method, recheck all above。
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//若进行到了此步操作,则表明工作线程数量加了1
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
//rolling back workCount
addWorkerFailed(w);
}
return workerStarted;
}
final void runWorker
上一节中,