状态定义
线程池ThreadPoolExecutor有两种属性:运行状态和线程数。
线程数很好理解,就是池子中有多少线程;运行状态共有5种,分别是
RUNNING:运行中, 为初始状态,即刚创建的线程池就是此状态。
SHUTDOWN:停工状态,不再接收新任务,但会继续处理队列中的任务。
STOP:停止状态,不再接收新任务,不再处理已有任务,且会中断正在执行的任务。
TIDYING:清理中,所有任务都停止了,且线程数量也降为0。
TERMINATED,终止状态,钩子函数terminated()已经执行完成,线程池彻底销毁。
复制代码
状态流转
五种状态的流转图如下
源码注释写的非常棒,此处贴一下
* RUNNING: Accept new tasks and process queued tasks
* SHUTDOWN: Don't accept new tasks, but process queued tasks
* STOP: Don't accept new tasks, don't process queued tasks,
* and interrupt in-progress tasks
* TIDYING: All tasks have terminated, workerCount is zero,
* the thread transitioning to state TIDYING
* will run the terminated() hook method
* TERMINATED: terminated() has completed
*
* The numerical order among these values matters, to allow
* ordered comparisons. The runState monotonically increases over
* time, but need not hit each state. The transitions are:
*
* RUNNING -> SHUTDOWN
* On invocation of shutdown(), perhaps implicitly in finalize()
* (RUNNING or SHUTDOWN) -> STOP
* On invocation of shutdownNow()
* SHUTDOWN -> TIDYING
* When both queue and pool are empty
* STOP -> TIDYING
* When pool is empty
* TIDYING -> TERMINATED
* When the terminated() hook method has completed
复制代码
代码实现
线程数是一个非负整数,可以用一个int来表示。
运行状态固定只有5种,为节省空间,可以用三个bit来表示,因为三个bit最多可以表示8种状态了。
线程池的这两个属性是需要同时访问的,也就是需要保证原子操作,如果按上面分开表示的话,修改时估计还得加一把锁。
那有没有更优雅的实现呢,我们来看一下Doug Lea大牛的实现吧
/**
* The main pool control state, ctl, is an atomic integer packing
* two conceptual fields
* workerCount, indicating the effective number of threads
* runState, indicating whether running, shutting down etc
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 表示线程数的bit数
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程池支持的最大的线程数量(536870911,完全够用了)
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits(运行状态保存在int的高三位)
// 通过注释的数字可以看出,运行状态对应的数字依次增大,这很重要,方便后续的运行状态判断
// 1110 0000 0000 0000 0000 0000 0000 0000(-536870912)
private static final int RUNNING = -1 << COUNT_BITS;
// 0000 0000 0000 0000 0000 0000 0000 0000(0)
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 0010 0000 0000 0000 0000 0000 0000 0000(536870912)
private static final int STOP = 1 << COUNT_BITS;
// 0100 0000 0000 0000 0000 0000 0000 0000(1073741824)
private static final int TIDYING = 2 << COUNT_BITS;
// 0110 0000 0000 0000 0000 0000 0000 0000(1610612736)
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl,全是原子操作
// 获取运行状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 获取线程数量
private static int workerCountOf(int c) { return c & CAPACITY; }
// 组装运行状态和线程数量,成为ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
/*
* 因为运行状态存在int的高三位,且依次增大,因此可以巧妙的通过大小比较得知两状态关系
*/
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
// 判断线程是否在运行
// 此处为啥用 < SHUTDOWN 而不是直接 = RUNNING 呢,因为ctl还有低29位表示的线程数呢
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
复制代码
Doug Lea巧妙的使用了位运算,用一个AtomicInteger解决了操作两个属性的原子性问题;同时把状态放在高三位,还可以方便的通过大小比较来的值线程池的状态。
关于为什么使用int而不是long,Doug Lea也很贴心的给出了解释
* In order to pack them into one int, we limit workerCount to
* (2^29)-1 (about 500 million) threads rather than (2^31)-1 (2
* billion) otherwise representable. If this is ever an issue in
* the future, the variable can be changed to be an AtomicLong,
* and the shift/mask constants below adjusted. But until the need
* arises, this code is a bit faster and simpler using an int.
复制代码
用29位表示线程数,已经可以支持5亿个线程了,一般我们会通过-Xss1024K来设置线程栈大小为1M,如果我们达到5亿个线程,光线程栈空间就需要占用至少500T的内存了,真的难以想象我们有一天能到达这个量级。
不过Doug Lea还是很严谨的考虑了这种可能,如果AtomicInteger不够用了,就改用AtomicLong,不过目前来说用AtomicInteger更快更简单,不过度设计,够用就好~
状态操作
状态判断
public boolean isShutdown() {
return ! isRunning(ctl.get());
}
public boolean isTerminating() {
int c = ctl.get();
return ! isRunning(c) && runStateLessThan(c, TERMINATED);
}
public boolean isTerminated() {
return runStateAtLeast(ctl.get(), TERMINATED);
}
复制代码
有了以上关于状态定义的解释,再看这里关于线程池状态的判断方法就很好理解了。
isShutdown:只要线程池不是RUNNING状态,便是关闭了。
isTerminating:终止中,既不是RUNNING状态,又没有到终止状态。
isTerminated:已终止,那高三位必须是011,因此ctl的值要大于等于TERMINATED。
添加工作线程addWorker
当新任务到来时,如果核心线程数未满或阻塞队列已满,都会添加工作线程,即调用addWorker方法。addWorker方法有两个入参,第一个是Runnable任务,即最新到来的这个任务,不过该参数可能为空;第二个参数表示添加的是否是核心线程。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// step 1
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
// step 2.1
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// step 2.2
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
// step 2.3
if (runStateOf(c) != rs)
continue retry;
// step 2.4
// else CAS failed due to workerCount change; retry inner loop
}
}
// step 3
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)
addWorkerFailed(w);
}
return workerStarted;
}
复制代码
接下来,我们依次看下具体的处理过程。
首先是一个带有retry标号的死循环,在java中标号一般用在多重循环中,用于方便的控制程序流向,此处正是一个双重死循环。
Step 1,在外层死循环中,判断了线程池的状态,
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
复制代码
这个判断条件写的很复杂,Doug Lea的风格就喜欢把很多处理逻辑写到一条大的判断逻辑中,不过也导致了代码理解起来很困难,这里我们把这个判断条件根据德摩根定律做下转换,就是
// 根据德摩根定律转换
if (rs >= SHUTDOWN && (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty()))
// 继续化简,去除括号,变成了三个条件的或运算,即满足三者中任意其一,都将添加失败
if((rs >= SHUTDOWN && rs != SHUTDOWN) ||
(rs >= SHUTDOWN && firstTask != null) ||
(rs >= SHUTDOWN && workQueue.isEmpty())) return false;
复制代码
简化完看起来就好理解一些了,我们分开来看三个子条件
条件一,(rs >= SHUTDOWN && rs != SHUTDOWN) 等价于 rs > SHUTDOWN,即线程池处于STOP、TIDYING、TERMINATED三个状态,根据开头状态定义,都不会再处理任务了,所以addWorker肯定是要失败的。
条件二,(rs >= SHUTDOWN && firstTask != null),SHUTDOWN是停工状态,不再接收新任务,firstTask不为空说明正在添加新任务,当然也要制止,更不用说rs > SHUTDOWN的情况了(见条件一)。
条件三,(rs >= SHUTDOWN && workQueue.isEmpty()),任务队列都空了,说明没有任务要做了,且线程池已经马上或者已经关闭了,那还添加工作线程干嘛,不用说,自然也要劝退。
Step 2.1,跨过外层循环中的状态判断这一关之后,就进入到了内层死循环,有一点层层闯关的味儿。在这一关内,首先判断线程数wc,如果入参core是true,就判断核心线程数是否已达上限,如果core为false就去判断最大线程数是否已达到,只要已达上限就都不能再继续添加线程了。
Step 2.2,通过CAS方式给ctl加一,也就是线程数加一。只要CAS返回成功,就可以跳出retry双重循环进入到下一关了(step 3)。
Step 2.3,CAS失败,只有一种可能,就是同时有其他线程也修改了ctl值。我们知道ctl包含了两个属性:运行状态和线程数,也就是说这两个属性都有可能发生了变化。因此先判断线程池状态是否发生了变化,如果变了,就重新回到第一关(retry外层循环),一夜回到解放前~
Step 2.4,这一步虽然没有对应的代码,但是也是有含义的,即ctl中的线程数改变了。这时候就没必要重新回到第一关,只需要从内层循环重新开始就可以了。
Step 3,ctl线程数已经增加1了,也不能光吆喝不干事啊,所以就开始真正的添加worker了。这一步比较简单,创建worker对象并添加到workers集合中。这一步加锁了,因为要重新检查ctl状态,并且要往workers这个HashSet中添加元素,我们知道HashSet不是线程安全的。
/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
private final HashSet<Worker> workers = new HashSet<Worker>();
复制代码
工作线程Worker
经过层层闯关才能创建成功的worker到底长什么样呢,现在就开始揭开庐山真面目了~
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
复制代码
Worker类实现了Runnable接口,同时继承了AQS抽象类,因此它也维护了一个同步状态state,取值共有三个:
-1: 初始状态,在Worker构造方法中赋值的,即刚new出来,还没有run起来
0: 无锁状态,后面可以看到,在非执行任务期间处于无锁状态
1: 加锁状态,处于加锁状态说明worker正在工作(执行提交的任务)
复制代码
tryAcquire方法实现中,只有在state是0的时候才能返回成功,说明不是可重入的。
interruptIfStarted方法,顾名思义就是如果worker启动了就中断掉,那怎么知道worker有没有start呢?通过state的三个状态就可以知道了,如果没有start,则state是初始值-1。所以方法中有一个判断就是state>=0,如果是非负数说明worker已经启动了。
看完Worker类,发现没有一个关于是否是核心线程的变量,那核心线程和非核心线程怎么区分呢?其实Doug Lea在线程池的实现中,所有的工作线程没有任何区别,核心线程数和最大线程数仅仅是用来控制线程池的一些处理流程的,比如上面的addWorker和下面的getTask。
Worker类中持有一个thread变量,thread变量是这么创建出来的:
this.thread = getThreadFactory().newThread(this);
复制代码
因此当线程start的时候就会执行Worker类的run方法,run方法又直接委托给了ThreadPoolExecutor的runWorker方法。
runWorker
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// step 1
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// step 2
while (task != null || (task = getTask()) != null) {
// step 3
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 {
// step 4
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;
// step 5
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// step 6
processWorkerExit(w, completedAbruptly);
}
}
复制代码
runWorker方法比较长,这里把关键的6步标了出来。
Step 1,既然已经run起来了,那首先unlock一下,即把state状态从初值-1置为0,以免后面无法加锁(上面也提到过,只有state是0的时候才能加锁成功)。
Step 2,取任务,task要么是创建worker时传入的第一个任务,要么就去阻塞队列中抢任务。
Step 3,取到任务之后马上要开始干活了,先加下锁,告诉别人我正在忙呢,就好比上厕所把门锁上了。
Step 4,真正干活,不能占着茅坑不拉屎啊~
Step 5,干完活了,成功完成一个任务,鸡腿加一,然后解锁,好让别人知道我闲下来了。
Step 6,while循环退出了,说明没有取到任务,要么是线程池关闭了,要么是非核心线程在超时时间内没有取到任务,不管是哪种情况,worker都得被销毁了。
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.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
复制代码
这里简单讲一下该方法的思路。方法里又是一个死循环,每次循环都会先判断线程池状态,如果已关闭,则直接给线程数减一并返回null,让工作线程退出。然后判断是否需要带超时时间,如果带超时时间且超时未取到任务就会进入下次循环,在下次循环中就要有一个线程退出了,因为已经超过了最大空闲存活时间了。那怎么保证并发情况下只有一个线程退出呢,核心就在于
if (compareAndDecrementWorkerCount(c)) return null;
复制代码
即通过CAS方式给线程数减一,只有CAS成功的线程才会退出,失败的线程则进入下次循环。而下次循环时,由于线程数减一了,timed变量就可能不再是true了。通过这种方式动态的维持线程数,而不需要每个worker维持一个核心标识,简化了实现,实在是妙~
processWorkerExit
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 {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
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
}
addWorker(null, false);
}
}
复制代码
在worker退出时,主要做以下几件事:
- 如果completedAbruptly为true,即worker是异常退出,则给线程数减一;因为正常退出的话,getTask方法已经减一了。
- 将当前worker的完成任务数累加到线程池的完成任务数上,用于统计信息;将worker从workers集合中移除;
- 有worker退出了,说明线程池可能有状况发生,因此尝试关闭一下线程池(下面还会有详细解释~)。
- 只要线程池还未到STOP状态,要么worker是异常退出,要么正常退出但没有足够的worker来处理已有任务,这两种情况都需要添加新的worker来补位,以免影响任务的处理。
关闭线程池
关闭线程池有两种方式,分别是
- shutdown:将线程池状态改为SHUTDOWN,并中断所有空闲线程,可以看到并没有停止已有任务的执行。
- shutdownNow:将线程池状态改为STOP,中断所有线程,不再处理任何任务,并将未处理的任务打包返回。
shutdown
下面先来看shutdown方法
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
复制代码
shutdown这会依次调用以下几个方法
- advanceRunState,还是通过死循环+CAS来修改状态。
- interruptIdleWorkers,通过tryLock得知加锁状态从而判断线程是否空闲,如果空闲就中断。
- onShutdown,钩子函数,留给子类进行扩展用。
- tryTerminate,这个方法稍微复杂些,下面解释下。
tryTerminate方法里又是一个死循环,首先还是判断下线程池的状态,如果是RUNNING或者SHUTDOWN但还有任务未处理,不能关闭;如果是TIDYING或TERMINATED,则没必要再次终止了,也直接返回。
如果线程数不为0,就中断一个空闲的worker,然后返回。这里为什么只中断一个空闲线程呢,主要是有一种场景:shutdown方法只中断空闲线程,如果线程正在执行任务则不会被中断,但是当这些线程执行完所有任务最终还是会变成空闲线程,从而执行processWorkerExit方法。上面看到了,processWorkerExit方法会调用tryTerminate方法,从而不断传播,最终将所有空闲线程中断。
接下来就很简单了,将状态CAS为TIDYING,CAS成功后就执行钩子函数terminated。执行完terminated方法后就可以将状态置为TERMINATED了,这也符合了开篇的状态转换图了。如果CAS失败,则进入下一次循环,继续各种状态判断。
当ctl状态置为TERMINATED之后,就通过termination.signalAll()来唤醒等待在termination条件队列上的线程了。等待的线程是什么,当然是调用线程池awaitTermination方法的线程了~
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();
}
}
复制代码
shutdownNow
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
复制代码
理解了shutdown方法之后,再来看shutdownNow就简单多了,区别主要有三个:
- 将线程池状态改为STOP。
- 中断所有worker线程,使用的是interruptIfStarted方法,即不管是否空闲。
- 从阻塞队列中取出所有未执行的任务并返回。
总结
- 线程池有5种状态,但是并不一定会都经历。
- Worker类继承AQS,通过加锁状态来判断是否空闲。
- 核心线程和非核心线程并没有做区分,只有两个数字用来控制线程池的不同处理逻辑。
- 看完线程池源码之后,我们会发现多线程并发处理非常复杂,有各种各样的情况需要考虑,源码中经常使用死循环+状态判断+CAS来处理。并发大牛Doug Lea为我们提供了久经考验的J.U.C并发工具包,因此一般情况下我们就不要自己造轮子了。