首先声明本文不会告诉你线程池基础的属性,以及线程池状态流转之类的,只硬核介绍他的核心动作实现原理。
本文从下面的几个问题展开讲解,带你更深入的学习ThreadPoolExecutor。
- 如何创建线程
- 当队列满了的时候,哪个时间点扩展线程池数量
- 空闲线程是如何销毁的
- 是如何判断大于核心线程数的线程到达了keepalive的时间的
- 空闲线程是如何不死亡的
- 空闲的线程有活了,如何被唤醒的
一个阻塞队列解决了很多的问题,很妙
1. 如何创建线程
1)核心的创建线程的方法
// firstTask:提交的任务可以为null,如果为null,那么单纯创建一个worker,如果不为null时,那么说明提交的任务导致需要扩大线程池的数量
// core: 是否是核心线程
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 {
// 创建worker
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
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;
}
2)线程运行实体
Worker继承了Thread类,重写的run方法体执行runWokrer方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 取出firstTask,其实也就是addWorker方法中的firstTask参数
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 这里进行判断,如果task是null,getTask阻塞返回了同时也为null,
// 那么说明当前线程没有任务需要处理,同时也是非coreThread,那么向下执行,线程结束
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);
}
}
3)重点分析getTask方法
private Runnable getTask() {
// 最后一次poll是否超时
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?
// 判断是否要用poll
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 当前线程数大于核心线程数(timed),同时已经超时过一次(timedOut),那么判断前半部分为true
// 后半部分基本都为true
// 所以减少工作线程的数量,注意这里只是减少数据,但是实际线程还在运行只不过不工作了。(为什么?因为是它自己去减少工作线程的数量的,所以肯定还在运行)
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;
// r==null,说明poll超时,重新循环
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
现在再来看下面的问题就很简单了。
2. 当队列满了的时候,哪个时间点扩展线程池数量
可以看下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.
*
* 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();
// 如果当前线程数量小于核心线程数,那么直接带着任务去创建线程,也就是说,【即使有空闲的线程,那么也会创建】
if (workerCountOf(c) < corePoolSize) {
// addWorker参数为command
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);
}
我们可以注意到一些细节,线程池进行扩容的时候,那么一定是有任务进来了,同时新的线程是直接去消费这个任务的,并不是先创建线程并且任务入队,然后该线程再从队列里面取任务执行。
3. 空闲线程是如何销毁的
并不是被销毁的,而是阻塞时间到了(keepAliveTime),同时线程池当前线程数量大于核心线程数,所以线程最终会退出runWorker的while循环,线程自动执行结束。
4. 是如何判断大于核心线程数的线程到达了keepalive的时间的
在getTask中
5. 空闲线程是如何不死亡的
被阻塞队列给阻塞了,所以不会死亡,具体原因,也就是AQS的事情了
6. 空闲的线程有活了,如何被唤醒的
这也是阻塞队列的事情
题外话:线程池如何关闭
shutdown():优雅的关闭
调用 shutdown()
方法之后线程池并不是立刻就被关闭,因为这时线程池中可能还有很多任务正在被执行,或是任务队列中有大量正在等待被执行的任务,调用 shutdown()
方法后线程池会在执行完正在执行的任务和队列中等待的任务后才彻底关闭。
但这并不代表 shutdown()
操作是没有任何效果的,调用 shutdown()
方法后如果还有新的任务被提交,线程池则会根据拒绝策略直接拒绝后续新提交的任务。
isShutdown()
它可以返回 true 或者 false 来判断线程池是否已经开始了关闭工作,也就是是否执行了 shutdown
或者 shutdownNow
方法。这里需要注意,如果调用 isShutdown()
方法的返回的结果为 true 并不代表线程池此时已经彻底关闭了,这仅仅代表线程池开始了关闭的流程,也就是说,此时可能线程池中依然有线程在执行任务,队列里也可能有等待被执行的任务。
isTerminated()
这个方法可以检测线程池是否真正“终结”了,这不仅代表线程池已关闭,同时代表线程池中的所有任务都已经都执行完毕了,因为我们刚才说过,调用 shutdown
方法之后,线程池会继续执行里面未完成的任务,不仅包括线程正在执行的任务,还包括正在任务队列中等待的任务。
shutdownNow():粗鲁的关闭
最后一个方法是 shutdownNow()
,也是 5 种方法里功能最强大的,它与第一种 shutdown
方法不同之处在于名字中多了一个单词 Now,也就是表示立刻关闭的意思。
在执行 shutdownNow
方法之后,首先会给所有线程池中的线程发送 interrupt
中断信号,尝试中断这些任务的执行,然后会将任务队列中正在等待的所有任务转移到一个 List 中并返回,我们可以根据返回的任务 List 来进行一些补救的操作,例如记录在案并在后期重试。