文章首发于有间博客,欢迎大伙光临有间博客
上一篇关于线程池,你所要了解的一些东西(一)大体介绍了多线程以及线程池在什么场景下会有其作用,并且介绍了内部的一些核心参数,这篇文章将具体介绍线程池的运行机制。话不多说,直接上代码!
execute()
这是线程池在执行一个任务时所调用的方法,我们看看是如何实现的
public void execute(Runnable command) {
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.
*
* 1、如果当前线程数小于核心线程数,那么就创建一个线程,并且
* 将该任务作为这个线程的第一个任务,调用addWorker()方法
* 时会原子性的检查runStatus和workerCount,如果addWorker失败,则返回
* 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.
*
* 2.如果任务能够被加入任务队列,然后需要二次检查是否增加一个线程
* 到池中,可能上一个存在的线程已经死亡了 ,而且当线程池已经不处于运行状态的时候,
* 回滚添加到工作队列中的任务。
*
* 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.
* 3. 如果任务队列已经满了,在不超过最大池的前提下将会去创建一个线程去做这个任务,
* 如果执行失败,执行拒绝策略。
*
*/
//获取当前线程池的状态和线程数
int c = ctl.get();
//如果线程数小于核心线程数
if (workerCountOf(c) < corePoolSize) {
//则执行创建线程并执行任务的操作
if (addWorker(command, true))
//成功后返回
return;
//如果失败了则重新获取状态值和线程数
c = ctl.get();
}
//线程池是否还是RUNNING状态,如果是的话就往工作队列中添加任务
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);
}
addWorker(Runnable firstTask, boolean core)
可以在上述的execute()中多次看见addWorker方法,这个方法有两个参数,第一个是需要执行的任务,以及创建的是否是核心池中的线程。下面是具体的方法
private boolean addWorker(Runnable firstTask, boolean core) {
//先为下面的for循环进行一个标识
retry:
for (;;) {
//获取当前线程池的状态和线程数
int c = ctl.get();
//获取当前线程池的状态
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//这里进行了一个判断,首先判断当前的线程池状态是否大于SHUTDOWN
//我们明白SHUTDOWN = 0, RUNNING < 0,所以如果rs >= SHUTDOWN就代表了不是可运行状态
//代表了不允许往线程池中添加新的任务,如果 > SHUTDOWN,则直接退出,如果 == SHUTDOWN
//那么就会判断,如果当前任务为空或者任务队列不为空的时候,会继续执行。
//任务为空代表了仅仅只是创建一个线程给予执行,队列不为空是在SHUTDOWN状态下继续执行完队列剩余的任务。
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
//如果不满足上述,返回false
return false;
//如果满足上述的要求,那么就是允许添加并执行任务了,设置一个for死循环,用于cas判断跳出
for (;;) {
//重新获取当前线程数
int wc = workerCountOf(c);
//如果当前的线程数已经达到最大值,或者
if (wc >= CAPACITY ||
//是否大于核心池大小或者最大池大小(看传入的core判断)
wc >= (core ? corePoolSize : maximumPoolSize))
//如果大于,说明线程数满了,直接返回
return false;
//通过cas的方式对线程数c进行添加
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
//不然则重新进行内部循环,重新进行cas的判断增加
}
}
//能执行到这里,代表上述流程已经走完,线程数名义上已经成功添加,下面就是真正添加线程的过程
//设置一个判断线程是否启动
boolean workerStarted = false;
//设置一个判断线程是否增加
boolean workerAdded = false;
Worker w = null;
try {
//根据任务生成一个工作线程,内部是指定的任务以及线程工厂创建的线程
w = new Worker(firstTask);
//将线程实体的线程进行取出来
final Thread t = w.thread;
//如果生成的线程不为null
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());
//如果状态是RUNNING或者是SHUTDOWN但执行的任务为空时
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;
//成功创建后,将判断添加的值设为true
workerAdded = true;
}
} finally {
//最后进行解锁
mainLock.unlock();
}
//如果成功添加线程
if (workerAdded) {
//将线程进行启动
t.start();
workerStarted = true;
}
}
} finally {
//如果没有成功
if (! workerStarted)
//将线程从线程集合中移除,并cas减少ctl的值
addWorkerFailed(w);
}
return workerStarted;
}
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
以上就是线程池如何添加线程以及任务的过程,可以比较清晰的看出,在最后如果满足要求是创建一个新的线程,如果有任务就将该任务作为第一个任务给线程执行。将线程启动,但是线程启动后是怎么执行的?它是怎么获取到任务队列的任务的?
runWorker(Worker w)
可以在上述看到启动线程的start的时候,会调用Worker的run方法,直接来看看run方法
public void run() {
//将worker委托给runWorker运行
runWorker(this);
}
//真正执行线程逻辑的代码,传入线程实体
final void runWorker(Worker w) {
//获取当前的执行线程
Thread wt = Thread.currentThread();
//以及之前封装进来的任务
Runnable task = w.firstTask;
//将worker封装的任务重新设为null
w.firstTask = null;
//worker的构造函数中setState(-1)禁止线程中断,所以这里unlock允许中断
w.unlock(); // allow interrupts
//用于标识是否突然终止
boolean completedAbruptly = true;
try {
//如果需要执行的任务不为null则优先执行现有的任务,
//不然则调用getTask()获取任务队列中的任务,详细在下面解读,获取的任务不为null
//则继续执行
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
//首先进行一个判断,判断当前的线程池是否处于STOP,TIDYING,TERMINATION
//如果处于而线程未中断,则中断线程
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 {
//最后设置任务为null
task = null;
//完成的任务数+1
w.completedTasks++;
//解锁允许中断
w.unlock();
}
}
completedAbruptly = false;
} finally {
//清理线程池中的数据。
processWorkerExit(w, completedAbruptly);
}
}
getTask()
再来看看getTask()方法,是如何从任务队列中取得任务去执行的
private Runnable getTask() {
//判断是否已经线程超时,如果有设置超时的话
boolean timedOut = false; // Did the last poll() time out?
//设置一个死循环,进入取任务
for (;;) {
//获取当前线程池的状态和线程数
int c = ctl.get();
//获取线程池的状态
int rs = runStateOf(c);
//如果当前的线程池状态是STOP及以上,则开始清除线程
//如果是SHUTDOWN但是队列为空,也开始清除线程
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
//减少可用线程数量,内部是do while循环,清除全部
decrementWorkerCount();
//返回空
return null;
}
//获取可用的线程数
int wc = workerCountOf(c);
//这里判断是否需要使用超时
//allowCoreThreadTimeOut 如果为true则代表核心线程有超时时间,
//或者线程数大于核心线程数,则其他线程都会需要使用超时
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//如果线程数已经大于最大池设定的线程数 或者 有设定超时并且已经超时并且线程数大于1或者任务队列为空不需要进行时
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
//执行减少线程,清除一个
if (compareAndDecrementWorkerCount(c))
//结束本段线程
return null;
//再次进入循环重新判断
continue;
}
//如果上述情况,如超时,线程池状态,线程数都没有问题,则执行下面的获取任务逻辑
try {
//如果有设置超时时间,那么就在超时时间内从任务队列中取任务
//如果没有设置超时时间,则take()直到有任务到来
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
//如果获取到的任务不为空
if (r != null)
//返回任务
return r;
//设置超时,代表线程在规定的时间内没有从任务队列中取到任务。
//然后又会进行循环,判断超时时间
timedOut = true;
} catch (InterruptedException retry) {
//设置异常就设置成未超时继续循环
timedOut = false;
}
}
}
总结
线程池在执行任务的时候,通过execute() 传入Runnable任务,进行线程池状态和线程的判断,如果没有什么问题则会根据情况加入任务队列或者是创建线程去执行任务。addWorker() 创建线程,并启动线程并对传入的任务进行执行。addWorker() 在调用start后进入runWorker() 进行执行目标线程的任务,如果传入的任务为空,则会根据getTask() 去任务队列中取出任务执行。整个流程都需要保证线程池的状态没有问题。
如果有什么地方标注错误请指出。