1 介绍
线程池主要解决两个问题:一是当执行大量异步任务时线程池能够提供较好的性能。二是线程池提供了一种资源限制和管理的手段,比如可以限制线程的个数,动态增加线程数。
2 ThreadPoolExecutor类图
推荐ProcessOn在线画图软件,高清无码请点击
Integer类型用32位二进制表示,其中高三位表示线程池状态,后面29位用来记录线程池线程个数。
// 默认是Running状态,线程个数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 线程个数掩码位数
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程的最大个数(低29位)00011111111111111111111111111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 线程池状态 高三位存储
// runState is stored in the high-order bits
// 高三位 11100000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
// 高三位 00000000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 高三位 00100000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
// 高三位 01000000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
// 高三位 01100000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
// 获取高三位(运行状态)
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 获取低29位(线程个数)
private static int workerCountOf(int c) { return c & CAPACITY; }
// 计算ctl的值(线程状态与线程个数)
private static int ctlOf(int rs, int wc) { return rs | wc; }
线程池状态:
- RUNNING:接受新任务并且处理阻塞队列里的任务
- SHUTDOWN:拒绝新任务但是处理阻塞队里的任务
- STOP:拒绝新任务并且抛弃阻塞队列里的任务,同时会中断正在处理的任务
- TIDYING:所有任务都执行完(包含阻塞队列里的任务)后,当前线程池活动线程数为0时,将调用terminated方法
- TERMINATED:终止状态。terminated方法调用完成以后的状态
线程池参数:
- corePoolSize:线程池核心线程个数
- workQueue:用于保存等待执行任务的阻塞队列,比如基于数组的有界ArrayBlockingQueue,基于链表的无界LinkedBlockingQueue,最多只有一个元素的同步队列SynchronousQueue以及优先级队列PriorityBlockingQueue等。
- maximumPoolSize:线程池最大线程数量
- ThreadFactory:创建线程的工厂
- RejectedExecutionHandler:饱和策略,当队列满且线程个数达到maximumPoolSize后采取的策略,比如AbortPolicy(抛出异常),CallerRunsPolicy(使用调用者所在的线程来运行任务),DiscardOldestPolicy(调用poll丢弃一个任务,执行当前任务),DiscardPolicy(丢弃,不抛异常)。默认AbortPolicy。
- keepAliveTime:存活时间。如果当前线程池中的线程数量比核心线程数量多,并且是闲置状态,则这些闲置的线程能存活的最大时间
- TimeUnit:存活时间的时间单位
3 源码分析
3.1 public void execute(Runnable command)
execute方法的作用是提交任务command到线程池执行。
ThreadPoolExecutor的实现实际上是一个生产消费模型,当用户添加任务到线程池时,相当于生产者生产元素,线程池里面的worker直接执行任务或者从任务队列里获取任务时,则属于消费者消费元素。
public void execute(Runnable command) {
// 如果command为null,则抛出NPE异常
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
// 获取当前线程的状态+线程个数变量的组合值
int c = ctl.get();
// 当前线程池中线程个数是否小于corePoolSize,小于则开启新线程运行
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 当前线程池中线程个数大于corePoolSize,如果线程池处于RUNNING状态,则添加任务到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
// 二次检查
int recheck = ctl.get();
// 如果当前线程池不是RUNNING状态,那么则从队列中删除任务,执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果当前线程池为空,则添加一个线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果队列满,则新增线程(当前线程大于corePoolSize,小于maximumPoolSize),新增失败,执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
3.2 private boolean addWorker(Runnable firstTask, boolean core)
下面分析新增线程的addWorker方法
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;
// 标记a
// 循环CAS增加线程个数
for (;;) {
int wc = workerCountOf(c);
// 如果线程个数超出,返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// CAS增加线程个数,同时只有一个成功
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
// CAS失败了,看线程池状态是否变化了,变化则跳到外层循环尝试重新获取线程池的状态,否则内层循环重新CAS
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 标记b
// 以下操作说明CAS已经成功
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 创建worker
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// 加独占锁,为了实现workers同步,可能多个线程调用了线程池的execute方法
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
// 重新检查线程池的连接状态,避免在获取锁之前调用了shutdown方法
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,目前线程池的最大线程数量
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 添加任务成功过后,启动线程,处理任务
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
代码比较长,主要解释一下标记a和标记b。
标记a:内层循环的作用就是使用CAS增加线程个数,如果线程个数超出范围,则返回false。没有超出范围,就使用CAS操作设置线程个数,CAS成功则退出双层循环;如果CAS失败了,就查看线程池的状态是否变化了,如果变化了,就再次进入外层循环重新获取线程池状态,否则进入内层循环继续进行CAS尝试。
标记b:获取独占锁,重新检查线程池状态,重新检查是为了避免在获取锁之前其他线程调用了shutdown关闭了线程池。如果线程池已经关闭,则释放锁,新增线程失败。否则添加工作线程到workers,然后释放锁。如果新增工作线程成功,则启动工作线程。
3.3 final void runWorker(Worker w)
用户线程提交任务到线程池后,由worker来执行。
下面是worker的构造函数
Worker(Runnable firstTask) {
setState(-1); // inhibit(抑制,阻碍) interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this); // 创建一个新的线程
}
在构造函数内首先设置Worker的状态为-1,这是为了避免当前worker在调用runWorker方法前被中断(当其他线程用了线程池的shutdownNow时,如果Worker状态>=0则会中断该线程),设置了线程的状态为-1,该线程就不会中断。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// 将status设置为0,允许中断
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 执行任务前干一些事情,默认空方法
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 执行任务
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
// 执行任务完成后干一些是事情
afterExecute(task, thrown);
}
} finally {
task = null;
// 统计当前Worker完成了多少个任务
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 执行清理工作
processWorkerExit(w, completedAbruptly);
}
}
如果当前task=null或者调用getTask从任务队列获取的任务返回null,执行processWorkerExit进行清理工作。如果task不为null,获取工作线程内部持有的独占锁,然后执行扩展接口代码在具体任务执行前做一些事情,task.run()具体执行任务,然后执行扩展接口代码在具体任务执行后做一些事情,统计当前worker完成了多少个任务,然后释放锁。
这里在执行具体任务期间加锁,是为了避免在任务执行期间,其他线程调用了shutdown后正在执行的任务被中断(shutdown只会中断当前被阻塞挂起的线程)。
下面解析getTask()方法
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 1.SHUTDOWN 以上的状态,并且workQueue为空
// 2.STOP以上的状态
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// 线程数减一
decrementWorkerCount();
// 该线程退出
return null;
}
// 下面都是RUNNING状态,或SHUTDOWN状态workQueue!=null
// 计算线程个数
int wc = workerCountOf(c);
// Are workers subject to culling?
// 当allowCoreThreadTimeOut为true或者当前任务数超过核心线程数时,timed为true
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 是否删除超时的线程
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 如果timed为true说明worker有可能要被关闭
Runnable r = timed ?
// 如果超过keepAliveTime纳秒还没取到任务,就返回null,后面会调用processWorkerExit把worker关闭
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
// timed = false,任务队列为空就阻塞在这里,直到任务队列再有任务
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
3.4 private void processWorkerExit(Worker w, boolean completedAbruptly)
/**
* If false (default), core threads stay alive even when idle.
* If true, core threads use keepAliveTime to time out waiting
* for work.
*/
// 线程池的成员变量,具体作用看英文注释
private volatile boolean allowCoreThreadTimeOut;
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 统计整个线程池完成得任务个数,并从工作集里面删除当前worker
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
// 尝试设置线程池的状态为TERMINATED,如果当前线程池状态是SHUTDOWN状态并且工作队列为空
// 或者当时线程池状态是STOP状态,当前线程池里面没有活动线程
tryTerminate();
//当线程池是RUNNING或SHUTDOWN状态时,如果worker是异常结束,直接添加一个线程
// 线程正常结束, 如果允许对core线程进行超时控制,并且任务队列中有任务, 则保证线程数量>=1
// 如果不允许对core进行超时控制,则保证线程数量大于等于corePoolSize
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
// 工作执行完,线程销毁
return; // replacement not needed
}
// 如果worker是异常结束,直接添加一个线程
addWorker(null, false);
}
}
统计线程池完成的任务个数,在统计前加了全局锁。把在当前工作线程中完成得任务累加到全局计数器,然后从工作集(workers)中删除当前worker。
如果当前线程池状态是SHUTDOWN状态并且工作队列为空,或者当时线程池状态是STOP状态,当前线程池里面没有活动线程,则设置线程池的状态为TERMINATED。如果设置为了TERMINATED状态,则还需要调用条件变量termination的signAll方法几乎激活所有调用线程池的awaitTermination方法而被阻塞的线程。
3.4 public void shutdown()
调用shutdown方法后,线程池就不会再接受新的任务了,但是工作队列里面的任务还是要执行的。该方法会立刻返回,并不等待队列任务完成之后再返回。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 设置权限检查
checkShutdownAccess();
// 设置当前线程池状态为SHUTDOWN,如果已经是SHUTDOWN则直接返回
advanceRunState(SHUTDOWN);
// 设置中断标志
interruptIdleWorkers();
// hook方法,默认为空,让用户在线程池关闭时可以做一些操作
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试将状态变为TERMINATED
tryTerminate();
}
checkShutdownAccess方法检查是否设置了安全管理器,是则看当前调用shutdown命令的线程是否有关闭线程的权限,如果有权限则还要看调用线程是否有中断工作线程的权限。如果没有权限则抛出SecurityException或者NPE。
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
如果当前线程池的状态>=SHUTDOWN跳出循环,直接返回;否则设置为SHUTDOWN状态。
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
// 首先看当前线程是否已经中断,如果没有中断,就看线程是否处于空闲状态
// 如果能获得线程关联的Worker锁,说明线程处于空闲状态,可以中断
// 否则说明线程不能中断
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
设置所有空闲进程的中断标志。这里首先加全局锁,同时只有一个线程可以调用shutdown方法设置中断标志,然后尝试获取Worker自己的锁,获取成功,则设置中断标志。由于正在执行的任务已经获取了锁,所以正在执行的任务没有被中断,这里中断的是阻塞到getTask()方法并试图从队列里面获取任务的线程,就是空闲线程。
final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 当前线程池的状态为以下几种情况时,直接返回:
// 1. RUNNING,因为还在运行中,不能停止
// 2. TIDYING或TERMINATED,因为线程池中已经没有正在运行的线程了
// 3. SHUTDOWN并且等待队列非空,这时要执行完workQueue中的task
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
// 只能是以下情形会继续下面的逻辑:结束线程池。
// 1.SHUTDOWN状态,这时不再接受新任务而且任务队列也空了
// 2.STOP状态,当调用了shutdownNow方法
if (workerCountOf(c) != 0) { // Eligible to terminate
// 如果工作线程数量不为 0,中断线程池中第一个线程
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 将线程状态设为 TIDYING,如果设置不成功说明线程池的状态发生了变化,需要重试
// 如果设置成功,则调用terminated方法
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
// 设置当前线程池的状态为TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
在tryTerminate ()方法中,当工作线程不为0的时候,会去尝试中断线程池中的一个线程,这样做的主要目的在于防止shutdown ()方法的中断信号丢失。
当shutdown ()方法被调用时,会执行interruptIdleWorkers(),此方法会先检查线程是否是空闲状态,如果发现线程不是空闲状态,才会中断线程,中断线程让在任务队列中阻塞的线程醒过来。但是如果在执行interruptIdleWorkers()方法时,线程正在运行,此时并没有被中断;如果线程执行完任务后,然后又去调用了getTask(),这时如果workQueue中没有任务了,调用workQueue.take()时就会一直阻塞。这时该线程便错过了shutdown() 的中断信号,若没有额外的操作,线程会一直处于阻塞的状态。所以每次在工作线程结束时调用tryTerminate方法来尝试中断一个空闲工作线程,避免在队列为空时取任务一直阻塞的情况,弥补了shutdown() 中丢失的信号。
使用CAS设置当前线程池状态为TIDYING,如果设置成功则执行扩张接口terminated在线程池状态变为TERMINATED前做一些事情,然后设置当前线程池状态为TERMINATED。最后调用termination.signalAll()激活因调用条件变量termination的awiat系列方法而被阻塞的所有线程(主要是用户线程)。
3.4 public List shutdownNow()
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 权限检查
checkShutdownAccess();
// 设置当前线程池状态为STOP
advanceRunState(STOP);
// 中断所有线程
interruptWorkers();
// 将队列任务移动到tasks中
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
首先权限检查,然后设置当前线程池的状态为STOP,随后中断所有线程,中断的所有线程包括:空闲线程和正在执行任务的线程。
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
// 不管线程是否空闲都执行中断
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
/**
* drainQueue()主要作用是清空任务队列,并将队列中剩余的任务返回
*/
private List <Runnable> drainQueue() {
BlockingQueue <Runnable> q = workQueue;
ArrayList <Runnable> taskList = new ArrayList < Runnable > ();
// 该方法会将阻塞队列中的所有项添加到 taskList 中
// 然后清空任务队列,该方法是线程安全的
q.drainTo(taskList);
if (!q.isEmpty()) {
// 将 List 转换为 数组,传入的 Runnable[0] 用来说明是转为 Runnable 数组
for (Runnable r: q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}
/**
* worker的内部方法
* 中断(如果运行)
* shutdownNow时会循环对worker线程执行
* 且不需要获取worker锁,即使在worker运行时也可以中断
*/
void interruptIfStarted() {
Thread t;
// 如果state>=0、t!=null、且t没有被中断
// new Worker()时state==-1,说明不能中断
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
3.4 public boolean awaitTermination(long timeout, TimeUnit unit)
当线程调用awaitTermination方法后,当前线程会被阻塞,直到线程池状态变为TERMINATED才返回,或者等待时间超时才返回。
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
if (runStateAtLeast(ctl.get(), TERMINATED))
return true;
if (nanos <= 0)
return false;
nanos = termination.awaitNanos(nanos);
}
} finally {
mainLock.unlock();
}
}
以上代码首先获取独占锁,然后在无限循环内部判断当前线程池状态是否至少是TERMINATED状态,如果是则直接返回,否则说明当前线程池里面还有线程在执行,则看设置的超时时间nanos是否小于0,小于0则直接返回,如果大于0则调用条件变量termination的awaitNanos等待nanos时间,期望在这段时间内线程池的状态变为TERMINATED。
如果在调用awaitTermination之前调用shutdown方法,并且在shutdown内部将线程池状态设置为TERMINATED,则termination.awaitNanos方法会返回。
工作线程Worker的runWorker方法内,当工作线程运行结束后,会调用processWorkerExit方法,在processWorkerExit方法内部也会调用tryTerminate方法测试是否应该把当前线程池的状态设置为TERMINATED,如果是,则也会调用termination.signalAll()激活调用线程池的awaitTermination方法而被阻塞的所有线程。
当等待时间超时后,termination.awaitNanos也会返回,这时会重新检查当前线程池状态是否为TERMINATED,如果是直接返回,否则继续阻塞挂起自己。
4 总结
线程池使用了一个Integer类型的原子变量来记录线程池状态和线程池中的线程个数。通过控制线程池的状态来控制任务的执行,每个Worker线程可以执行多个任务。线程池通过线程的复用减少了线程创建和销毁的消耗。
这块代码确实是挺难理解了,笔者也是理解了好长时间,Doug Lea大神用到了AQS和CAS,没有这里的基础,理解起来会相对困难点,希望读这篇文章的人,可以理解笔者的良苦用心,有什么问题,就写在下面的评论里面,笔者会尽力解答的。
5 参考文章
《Java并发之美》
《码出高效》
《Java多线程编程实战》