jdk自带线程池ThreadPoolExecutor包含了大量的信息,其中包括真正的线程池实现,工作队列,线程池状态,线程池的统计信息(工作线程数,完成任务数)以及为了使线程池适配各种各样场合而产生的各种可调整参数以及钩子方法。使用Executors种的各种便利工厂方法基本已经可以满足日常情况下的需求。这里处于学习目的研究一下其工作机理。
线程池状态是控制线程池生命周期至关重要的属性,这里就以线程池状态为出发点,进行研究。
需要说明的是,在查看jdk1.6的源码和jdk1.7的源码时发现jdk1.7对线程池做了略微的修改,线程池状态由jdk1.6的RUNNING,SHUTDOWN,STOP,TERMINATED四个状态变为了RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED五个状态,并且修改线程池最大上限为2的29次方-1(而不是2的31次方-1),并将状态值与工作线程数打包在一个整型中,用整形的上3位表示状态,下29位表示工作线程数,这个属性字段为"ctl",下面将直接使用“ctl状态”来描述当前线程池状态,用“ctl计数”来描述当前线程池工作线程计数而不再解释。(这个属性为AtomicInteger ,主要是为了保持工作线程数的原子性)。因为手头只有jdk1.7,这次的随笔将以jdk1.7版本作为标准。
这里先列出两种可能的线程池状态流程:1.RUNNING –>SHUTDONW –>TIDYING –>TERMINATED
2.RUNNING –>STOP –>TIDYING –>TERMINATED
RUNNING
线程池初始状态为RUNNING,这在ctl属性初始化时进行了设置:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
RUNNING状态为线程池的运行态,运行态时,RUNNING将提供丰富的操作来使用线程池,例如可以使用prestartAllCoreThreads()方法启动所有核心线程,可以使用prestartCoreThread()启动单个核心线程等等。大部分情况下,使用做多的是execute(Runnable)方法,通过调用线程池的execute(Runnable)方法,可以执行Runnable对象的run()方法而不用去关心具体是哪个线程去执行的这个方法,线程的调控由线程池负责。execute(Runnable)方法代码如下:
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); }
execute执行时:
1.调用workCountOf()方法获取当前线程池中线程数量,判断当前线程数量是否超过了核心线程数,若未超过,则直接添加一个核心线程,并用此线程完成当前提交的任务。
2.若当前线程数已经超过核心线程数且ctl状态等于RUNNING且工作成功进入工作等待队列,则我们进一步复查ctl,若ctl状态依旧为RUNNING,且调用workCountOf()方法检查发现此时的工作线程数为0时,将添加一个工作线程;若此时已经不为RUNNING,则尝试移除任务,并调用拒绝任务方法:reject(command)。(这里之所以需要复查ctl状态是由于在执行workQueue.offer(command)方法时,ctl状态随时可能由于调用shutDown方法或者shutDownNow方法而发生变化)。
3.如果上述两种情况都不吻合,即此时已经有超过核心线程数的的线程在工作,且任务队列也已堆满,则尝试增加一个工作线程(如果此时线程数达到限定最大线程数,则会失败),若失败则调用拒绝任务方法reject(command).
在execute的方法中添加工作线程的所调用的法为addWorker(Runnable runnable,Boolean core).该方法接受两个参数,runnable对象为这个新建线程的第一个工作任务,可以为空。core指代新建的任务是否是核心线程。
addWorker,runWorker,getTask方法为线程池工作的主要方法,下面一一介绍。
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; for (;;) { int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { final ReentrantLock mainLock = this.mainLock; w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int c = ctl.get(); int rs = runStateOf(c); 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; }
逐行查看:
1.循环中首个判断条件:
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
这表示ctl状态为RUNNING状态或者为SHUTDOWN状态且此时任务队列仍有任务未执行完时,可以继续调用addWorker添加工作线程,但不能新建任务,即firstTask参数必须为null.否则这里将返回false,即新建工作线程失败。
2.内层循环:
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
这部分对当前线程数量做了判断,依据core参数,若core参数为true,即添加的为核心线程,则当前工作的线程数量不应当超过corePoolSize,否则返回false。若core参数为false,即添加的为普通线程,则当前工作的线程数量不应当超过maximumPoolSize,否则返回false。通过上述校验,则调用compareAndIncrementWorkerCount(c)尝试增加当前ctl计数,若成功则跳出外曾循环。若失败则重复价检查当前线程池ctl状态(之所以检查ctl是因为造成失败的原因为ctl发生变化,这有两种可能,一种是作为下29位的工作线程计数发生变化,一种是作为上3位的状态标志位发生变化,这里检查的就是上3位的变化),若是ctl状态发生变化,则重新尝试外层循环(这是有可能外层循环会由于ctl状态的变化而直接return false)。若是ctl计数发生变化,则重新尝试内层循环。
3.添加worker
try{
final ReentrantLock mainLock = this.mainLock;
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
int rs = runStateOf(c);
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);
}
这部分获取了线程池的主锁mainLock。从而保证对共享资源workers的排他访问。这里通过new Worker(firstTask) 新建了一个worker对象,该对象持有他的第一个任务firstTask。Woker类的构造方法,将通过ThreadFactory获取一个thread对象,这里可能失败,返回null,或者抛出异常。所以在上面的代码段中对这种情况作了处理,若返回null,则workerStarted将为false。将执行addWorkerFailed方法处理失败情况;若抛出异常同样workerStarted将为false。将同样执行addWorkerFailed方法。若成功通过ThreadFactory新建了一个持有当前worker对象的thread对象,则继续往下recheck ctl状态(规则与1没有区别),通过校验则将当前worker放入workers数组中,然后重新校正队则池大小(largestPoolSize),置workerAdded标志位为true。最后通过wokerAdded标志位校验,置workerStarted标志位为true,启动线程,该线程持有当前worker对象,会执行worker对象的run方法,而woker对象的run方法又调用了自身的runWorker(this)方法。至此为止一个worker正式被添加进入workers数组并且正式开始运转。
runWorker方法代码如下所示:
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; 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; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
逐行查看:
1.先说明一个变量completedAbruptly,这个布尔值被用于指代runWorker停止的原因,当completedAbruptly为true时代表worker停止是因为worker执行的外部业务逻辑代码抛出异常引起的。当completedAbruptly为false时代表worker停止是线程池内部工作机制下的正常退出。
2.获取当前执行线程,获取当前worker的第一个任务置于task变量中,之后执行w.firstTask = null。若不不将worker对象的firstTask置为空,则firstTask引用指向的task对象将不会被gc回收。将worker对象解锁,从而允许其在尚未获得task之前被中断。进入while循环,这里将调用getTask()方法获取任务task对象(可能造成线程等待),若因为各种原因(下面说明getTask()方法时会说到)返回null(即获取不到任务)则停止while循环,将completedAbruptly置为false。
3.进入循环内部,代表已经获取到可执行的task对象,则锁定worker对象(保证不被shutDown方法中断),接着做条件判断,若ctl状态超过STOP态,或者当前线程已经因为线程池内部状态变化而被中断(如何判断中断是因为线程池内部状态变化而中断的?重复检查ctl状态即可,若线程的中断信号是在外部逻辑代码中设置的(不使用线程池的shutDownNow方法,直接使用的thread.interrupt()方法),则ctl状态不会为STOP,这样就可判定为不是因为线程池内部状态变化而引起的中断),则设置该工作线程为中断状态(wt.interrupt())。否则执行beforeExecute(wt,task)钩子方法,用户可以重写该方法而达到一些自定义需求(例如统计)。接着执行获得task的run方法,至此该worker成功获得并执行了一个任务。执行期间抛出的异常都有处理,不再多说。最终将置task为空,增加worker的完成任务数(completedTasks),随后解锁worker。
4.在最外层的finally块中,执行了processWorkerExit(w,completedAbruptly)方法,该方法会处理处理统计信息,将worker的completedTask累加入线程池的completedTaskCount,并从线程池的workers中移除该worker,之后尝试终止线程池(因为这个worker可能是当前线程池中最后一个worker,tryTerminate方法应该在所有可能终止当前线程的地方被调用)。最后根据completedAbruptly的值选择策略,若为true,则直接调用addWorker增加一个新worker用以替代当前移除的worker;若为false则判断当前线程池大小是否小于允许的最小线程池大小(可能是corePoolSize也可能小于),若小于则调用addWorker增加一个新worker,否则什么也不做。
getTask方法代码如下所示:
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? retry: 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; } boolean timed; // Are workers subject to culling? for (;;) { int wc = workerCountOf(c); timed = allowCoreThreadTimeOut || wc > corePoolSize; if (wc <= maximumPoolSize && ! (timedOut && timed)) break; if (compareAndDecrementWorkerCount(c)) return null; c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
逐行查看:
1.同样首先说明一个变量,timeOut,这个布尔值指代当前getTask方法是否超时未能获取到task对象。
2.外层for循环首先校验ctl状态,若ctl状态大于SHUTDOWN或者等于SHUTDOWN且任务队列workQueue为空,则返回null。否则进入内层循环,若当前线程数量小于maximumPoolSize且并未超时或者当前线程池不存在超时限制(由timed变量指代),则跳出循环,否则则尝试减小ctl计数,并返回null。若尝试减小ctl计数失败(由于ctl值变化引起,上面addWorker中第2点已经提到过),则先检查ctl状态,若失败是由ctl状态变化引起,则尝试外层循环,若失败是由于ctl计数变化而引起则尝试内层循环。
3.在第二点中,跳出循环之后,将根据timed的值做不同策略。若timed为true,即线程池存在超时限制,则执行workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS),这个方法将只在keepAliveTime时间内等待获取任务,一旦超过则返回null;若timed为false,则执行workQueue.take()方法,该方法将无限时等待任务,直至获取任务或者出现中断异常。在这两个方法之一返回之后,若返回task为null,则设置timeOut为true,尝试外层循环;若task不为null,则返回成功获取的task对象。
4.如果在getTask期间出现中断异常,则设置timeOut为false,并且重试外层循环,即InterruptedException并不会对getTask方法造成任何影响,真正能影响getTask方法的是ctl状态的转变。
上面详细描述了线程池RUNNING状态下的三个主要的方法,addWorker,getTask,runWorker,这三个方法构成了线程池的主要工作流程,如下所示:
addWorker—>runWoker—>getTask—>runTask—>getAnotherTask—>runTask….
以上就为RUNNING状态下的基本流程,下面介绍RUNNING状态是如何跳转至SHUTDOWN和STOP状态。
SHUTDOWN
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(); }
1.调用shutdown()方法,将获得主锁mainLock,并且查看是否有执行shutdown的权限,然后调用advanceRunState方法,将ctl状态置为shutdown。调用interruptIdleWorkers()方法关闭尚未获得task对象的worker(即还未执行到getTask()方法或者还未得到getTask()返回的worker)。之后调用onShutdown()钩子方法,用户可以在这里处理自定义逻辑。最后调用tryTerminate()方法尝试终止线程池。
STOP
STOP状态由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; }
1.shutdownNow()方法将ctl状态置为STOP状态,并且调用 interruptWorkers()方法(注意不是interruptIdleWorkers方法,该方法不管worker是否获取到task对象,都会将worker所在的线程置为interrupt状态),之后移除任务队列中的所有任务。最后调用tryTerminate()方法尝试终止线程池。
至此线程池进入SHUTDOWN或者STOP状态,在shutdown和shutdownNow,以及之前在runWorker中提到过的processWorkerExit方法中都调用了tryTerminate()方法,这个方法将使线程进入过渡状态TIDYING,并且最终变为STOP状态。
TIDYING,STOP
TIDYING,STOP状态由tryTerminate()方法产生,代码如下所示:
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 } }
逐行查看:
1.tryTerminate()方法尝试终止线程池活动,满足终止条件的因素有两个:首先,ctl状态为STOP,或者为SHUTDOWN且任务队列为空(STOP状态之所以一直都不用判断workQueue是因为上面讲到的,shutdownNow()方法调用了drainQueue()方法清空了workQueue所以其必然为空,这里解释一下);其次,ctl计数为0。第二个条件的满足是由一连串连锁反应保证的,shutdownNow()方法置ctl状态为STOP,使得所有worker调用getTask()方法满足rs>=SHUTDOWN条件从而调用decrementWorkerCount()方法,这将最终导致ctl计数为0,同时所有work都将从getTask()方法获得null,最终导致runWorker()方法调用processWorkerExit()方法,将workers数组真正清空。shutdown()方法稍微复杂,它置ctl状态为SHUTDOWN,但是线程池仍将继续运行,直至所有workers将工作队列中的任务全部完成,之后的逻辑和stop状态下的完全一样,不再多说。
2.在保证了上述两个条件之后,tryTerminate()方法获取住所mainLock,置ctl状态为TIDYING,之后执行terminated钩子方法,最后置ctl状态为TERMINATED。
至此ctl状态变为TERMINATED,且workers已清空,workQueue也已清空,线程池的生命周期到此结束。
线程池关于线程池工作信息的统计方法,拒绝任务请求方法,以及为了适应各种工作环境的构造方法由于过于琐碎且和本文主线不符,这里就不再介绍。以上为本文全部,欢迎探讨。