ExecutorService
ExecutorService
是一个接口,此接口继承自Executor接口,Executor接口只有一个方法的定义就是execute接收了一个参数Runable,Runable接口中只有个方法run,结构如下。
Executor---(依赖)--->Runable
↑(继承)
ExecutorService
↑(实现)
AbstractExecutorService
↑(继承)
ThreadPoolExecutor
复制代码
由上方结构可看出Executor执行器结构最中执行的是Runable的run方法,因为他是接口所以具体的执行是在他的实现类中ThreadPoolExecutor。
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);
}
复制代码
上方ThreadPoolExecutor的execute方法就是对Executor接口的实现,以下是对该方法的解读。
- if (command == null) throw new NullPointerException();
判断了传入的Runable是否为Null如果是Null则抛出。
- int c = ctl.get();
获取ctl值。下面是对ctl值得操作
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static final int COUNT_BITS = Integer.SIZE - 3; private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; 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; } private static int ctlOf(int rs, int wc) { return rs | wc; } 复制代码
- private static final int COUNT_BITS = Integer.SIZE - 3;
ThreadPoolExecutor最大的work工作数则是5亿多,"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"这是官方说的说法WorkerCount的最大限制是5亿而不是20亿之所以说5亿是因为Integer.SIZE是32,而从代码中看出使用32-3则是29,2的29次方则是536870912。所以这里减3。
- private static final int CAPACITY = (1 << COUNT_BITS) - 1;
计算结果则是536870911,计算流程1的二进制是00000000000000000000000000000001,向左移动29位00100000000000000000000000000000,这个二进制值是536870912而最后又减去了1则是536870911。
- private static final int RUNNING = -1 << COUNT_BITS;
当前ThreadPoolExecutor的运行状态为RUNING运行运行,状态值为-536870912。代表接受新的任务并且处理队列任务。
- private static final int SHUTDOWN = 0 << COUNT_BITS;
当前ThreadPoolExecutor的运行状态为SHUTDOWN关闭状态,状态值为0。代表不接受新的任务,但是还在处理队列任务。
- private static final int STOP = 1 << COUNT_BITS;
当前ThreadPoolExecutor的运行状态为STOP停止状态,状态值为536870912。代表不接受新任务,不处理队列任务,并且中断正在运行的任务。
- private static final int TIDYING = 2 << COUNT_BITS;
当前ThreadPoolExecutor的运行状态为TIDYING整理状态,状态值为1073741824。代表已经完成了所有任务正在做线程的状态转换整理。
- private static final int TERMINATED = 3 << COUNT_BITS;
当前ThreadPoolExecutor的运行状态为TERMINATED整理完成状态,状态值为1610612736。代表线程状态已经整理完成。
小结:从上面的定义和数值可以看出每种状态都有536870912个数值也就是任务可以操作,所以在这里java的作者使用了一个Integer的二进制数值做了他们的状态处理具体如何处理呢请看下面的方法。(补充:状态存在于高位。)
- private static int runStateOf(int c) { return c & ~CAPACITY; }
runStateOf就是根据当前的数字计算并且获取运行状态,如果c为
-536870912~-1
都会返回RUNNING,如果c为0~536870912
都会返回SHUTDOWN,如果c为536870913~1073741824
都会返回STOP,如果c为1073741825~1610612736
都会返回TIDYING,如果c为1610612737~Integer.MAX_VALUE
都会返回TERMINATED,下面以RUNNING为例讲解他的流程。-536870912的二进制码为:11100000000000000000000000000000 -1的二进制码为: 11111111111111111111111111111111 -536870900的二进制码为:11100000000000000000000000001100 上面笔者采用了-1和-536870900的作为c的例子传参,在runStateOf方法中可以看出c传入后与-536870912做了&操作,这个操作的意义是相同则取不同则抛弃,则最终的结果都为:11100000000000000000000000000000,读者可以根据这个例子去获取其他的状态加深学习。 复制代码
- private static int workerCountOf(int c) { return c & CAPACITY; }
workerCountOf用来获取任务个数的,前面有说过RUNNING状态下-536870912~0之间的数则是代表任务个数,因为是负数的原因所以这里&操作的是CAPACITY也就是536870912,这里需要弄清楚正数负数在二进制中的表现其中需要关注的点是原码、反码、补码,虽然网上有很多说明但是这里还是进行讲解以便跟上笔者的思路。
以-1为例,二进制值为: 11111111111111111111111111111111 补码: 11111111111111111111111111111111 反码: 11111111111111111111111111111110 原码: 10000000000000000000000000000001 可以从上方看出最终计算机存储的是补码,为什么这么麻烦呢,因为计算机存储的是二进制不是1就是0,并不知道这是正数还是负数,所以需要最高位作为符号位出现如果是1则代表为负数如果是0则代表正数,所以在c中存在有符号和无符号这就是原因。说了原因那么就继续说一下他们之间的关系。 原码:扫一眼就知道值是-1对人类非常友好,但是对于计算机非常不友好,因为如果-1+1计算机是算不出来的,可以通过上方例子看下,在这种情况下计算出的是-2,因为的确是加一的只不过上一位是0所以进了一位就结束了,但是二进制阅读是-2所以就出现了问题,那么怎么解决这个问题呢?大佬们就提出了反码给他进行取反所以出现了反码。 反码:除了符号位其他为全部取反,但是又出现了问题那么就是使用反码加一应该是0结果呢居然是一串子1,0则表示为一串子0,这样就出现了0有两种二进制表达式要么全是1要么全是0,这肯定也是不行的所以大佬们又提出了补码。 补码:反码的结果进行加一所以可以看出上方二进制表达全是1就是这样来的,怎么去通过补码计算源码呢?看笔者上方例子,先减一则获取到反码再取反则获取原码。 复制代码
说了半天仅仅是为了叫大家更好的理解如果获取任务数的因为需要上方的知识,那么继续。 还是以-1为例传入workerCountOf,则获取到的数目是:536870911,如果有疑惑的同学可以参照上方以RUNNING为例做的&操作流程进行计算看看结果是否一样。
- private static int ctlOf(int rs, int wc) { return rs | wc; }
传入指定状态和任务数,获取当前累计数的具体值,前面说过这五个状态他们之间的数值区域,这里就需要配合这个区域进行讲解,如果状态为SHUTDOWN个数为2则返回2,如果状态为STOP数值为2则返回536870914。
这里使用SHUTDOWN状态和2作为案例讲解。 SHUTDOWN 二进制:00000000000000000000000000000000 2 二进制:00000000000000000000000000000010 结果 二进制:00000000000000000000000000000010 复制代码
可以上方案例看出
|
操作符就是只要二者有一则就取。- private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
之所以最后来解释这个方法就是因为他依赖了之前讲的信息。AtomicInteger是Integer原子操作类,一句话线程安全(什么是线程安全这里暂不介绍),对于他的讲解下次再讲不然他又可以写一大篇了,可以看出最终他的初始值是RUNNING状态即-536870912,而ctl就是用来记录当前的任务数和状态的后面会大量的使用,具体为什么能记录任务数和他的状态如果存在这个疑问的同学请重头再看下本文。
- int c = ctl.get();
继续上方的源码解读,可以看到int c就是获取前面介绍的ctl中存储的值。
if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } 复制代码
这段代码workerCountOf根据ctl的状态值获取存储的任务数,corePoolSize在ThreadPoolExecutor初始化的时候设置的核心任务迟的数量,如果当前任务数小于设置数则添加到任务中,如果添加失败则从新获取ctl值,前面说过ctl是线程安全的所以可能在别的线程有操作了ctl所以需要重新获取,这里addWorker方法暂不介绍下方会讲解。
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); //-------------------------无敌分割线------------------------------- private static boolean isRunning(int c) { return c < SHUTDOWN; } private volatile RejectedExecutionHandler handler; final void reject(Runnable command) { handler.rejectedExecution(command, this); } 复制代码
如果上方添加失败或者当前的任务数已经大于了初始化时设值得任务数
则先判断当前的状态是否为RUNNING状态,如果是则将任务添加到任务的队列中添加成功后
再次获取ctl值并且再次判断是否为运行状态如果不是则从队列中删除,并且调用reject方法,该方法调用了handler对象的rejectedExecution。
RejectedExecutionHandler源码及实现。
//拒绝执行的处理器,使用于当前运行的任务已满时则需要需要拒绝执行,拒绝执行时的处理 //这里的已满并不是设置的corePoolSize而是CAPACITY即536870912 public interface RejectedExecutionHandler { void rejectedExecution(Runnable r, ThreadPoolExecutor executor); } //调用执行处理器,如果被拒绝了则判断当前的线程池并没有关闭然后在当前线程调用run进行执行 public static class CallerRunsPolicy implements RejectedExecutionHandler { public CallerRunsPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } } //中止处理器,如果被拒绝则抛出异常停止处理当前的任务。 public static class AbortPolicy implements RejectedExecutionHandler { public AbortPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } } //丢弃处理器,不做任何处理直接丢弃当前任务。 public static class DiscardPolicy implements RejectedExecutionHandler { public DiscardPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } } //原来的丢弃处理器,丢弃的是队列中的头一个而不是当前的,可以看出将任务队列中的第一个任务进行可弹出,然后再讲当前的丢进执行器然后执行器在经过前面讲的流程进行判断添加到任务队列中。 public static class DiscardOldestPolicy implements RejectedExecutionHandler { public DiscardOldestPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } } 复制代码
如果正在运行状态或者删除失败则判断当前的工作数是否为0如果是则调用addWorker,请留意调用addWorker的传参后面会提到。
<!--md内嵌符号降级--> 复制代码
如果当前不是运行状态或者队列添加任务时失败则调用addWorker。
终于到期待已久的addWorker了。修改之前讲解方式。将代码讲解作为注释写出
//两个参数firstTask,execute传入的runnable,core 是使用核心维护的线程数corePoolSize进行判断还是使用配置的maximumPoolSize最大线程数进行判断。
private boolean addWorker(Runnable firstTask, boolean core) {
//定义循环跳转标签retry
retry:
for (;;) {
//获取当前ctl的数值
int c = ctl.get();
//根据当前数值进行计算获取当前的线程池状态
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//条件解释:1、如果rs >= SHUTDOWN 代表STOP、SHUTDOWN、TIDYING、TERMINATED
//条件解释:2.1、如果状态为SHUTDOWN并且任务是null并且workQueue不为空,这三个条件任何一个为false并且条件1位true则返回添加失败。
//原因条件1:前面有说过SHUTDOWN、SHOP的意义(可以再这两个常量声明的地方查看),他们都有共同点都不在添加新的任务。
//原因条件2:是给如果rs是SHUTDOWN状态的附加条件,
// 1、可以分析出如果当前状态不是SHUTDOWN并且是STOP、TIDYING、TERMINATED中的一员那么返回false而在外边有个小括号并且进行了取反,这样的话直接返回false。
// 2、如果当前状态是SHUTDOWN并且firstTask不为null则代表是新增的任务也是直接返回false。
// 3、如果当前状态是SHUTDOWN并且firstTask为null,则代表是操作队列中的数据所以这个if就会跳过,后面会有更详细补充,暂且知道逻辑。
// 4、如果当前状态是SHUTDOWN并且firstTask为null并且当前的任务池的任务不为空又进行的取反则返回true在外部括号又进行了取反则是false,继续向下执行,此条件与条件三的原因一样,因为存在其他调用方认为还有任务池中的任务没有执行从而操作,但是在操作时发现已经执行完了所以返回false任务添加失败。
//总结:此if根据各个情况过滤任务。
// 1、状态在STOP、TIDYING、TERMINATED之中则立即返回添加失败
// 2、SHUTDOWN状态较为特殊因为此处状态描述就是继续运行任务池中的任务,不允许新增任务,这个if完成了这一状态的描述。
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//如果这个任务需要执行则再次循环处理
for (;;) {
//获取当前的任务数
int wc = workerCountOf(c);
//如果任务数已经大于等于系统的限制536870912个则直接返回false
//如果任务数小于系统限制536870912则判断core是否为true如果为true则代表使用corePollSize作为最大数值判断否则使用maximumPoolSize作为最大数判断限制值
// 需要注意他俩是或者的关系,二者满足其一即可返回false。
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//此方法就一句话return ctl.compareAndSet(expect, expect + 1)
//expect就是参数c,这里的操作是在比较c的数值是否和传入的c一样,如果一样则进行赋值返回true,否则赋值失败返回false。
//上面的第二句话有点绕这里解释下:因为是多线程操作你并不知道当前的数值是不是最新的了如果是那么则和传入的c两个数是相等的否则说明换已经有别的线程更新了ctl的值了当前线程的c则是老数据无用了所以赋值失败。
//如果成功则跳出retry之前说过的循环跳出的别名
if (compareAndIncrementWorkerCount(c))
break retry;
//上方赋值失败代表着数值需要更新所以这里又获取了次
c = ctl.get(); // Re-read ctl
//获取到最新的ctl数值则在进行判断当前的状态是不是rs就是老的ctl的状态如果不是了则代表状态也更新了则跳出当前的循环重新循环retry循环,用于重新校验。
//如果获取到的ctl状态和rs一样则不需要重新去校验继续当前的循环,将当前的循环逻辑再来一遍。
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//上方的死循环只有一处能活着来到这里那就是compareAndIncrementWorkerCount方法设置数值成功了。
//成功后声明三个变量任务已经启动,任务已经添加到队伍中,定义了任务对象
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//首先将任务对象创建处理,暂不提内部实现稍后再细说
w = new Worker(firstTask);
//可以看出在任务对象中有个线程的属性
final Thread t = w.thread;
//如果任务中的线程创建失败那么则进入finally中
if (t != null) {
//线程不为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.
//根据ctl值获取当前的线程池状态
int rs = runStateOf(ctl.get());
//如果状态小于SHUTDOWN那么就代表当前的状态是RUNNING
if (rs < SHUTDOWN ||
//如果当前状态是SHUTDOWN并且任务时null代表着需要执行队列中的任务
(rs == SHUTDOWN && firstTask == null)) {
//如果当前线程已经启动了那么抛出异常
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//将worker添加到workers的set中
workers.add(w);
//获取当前的workers大小
int s = workers.size();
//如果当前的set的大小已经超过了目前执行的最大线程数那么久更新最大线程数为当前的workers的大小
if (s > largestPoolSize)
largestPoolSize = s;
//设置workerAdded为true
workerAdded = true;
}
} finally {
//释放锁
mainLock.unlock();
}
//当前workerAdded设置为true时启动该线程,并且设置workerStarted为true
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//如果任务创建失败那么workerStarted铁定是false的,所以这里走了addWorkerFailed方法。
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
//任务添加失败则会进入该方法。
private void addWorkerFailed(Worker w) {
//获取当前对象中声明的线程锁
final ReentrantLock mainLock = this.mainLock;
//加锁
mainLock.lock();
try {
//如果w不是null则删除在workers中的存储。
if (w != null)
workers.remove(w);
//ctl数值减一
decrementWorkerCount();
//尝试终止当前运行的操作
tryTerminate();
} finally {
//最后解锁
mainLock.unlock();
}
}
//ctl数值循环减一方法
private void decrementWorkerCount() {
//compareAndDecrementWorkerCount方法的一句话代码:ctl.compareAndSet(expect, expect - 1);
//在添加任务时进行了加一这里操作就是减一。因为在添加的时候有for的循环所以这里封装了专门的方法进行了循环操作。
do {} while (! compareAndDecrementWorkerCount(ctl.get()));
}
final void tryTerminate() {
for (;;) {
//获取ctl存储的数值
int c = ctl.get();
//如果当前线程池状态是正在运行则直接返回不会尝试终止
if (isRunning(c) ||
//如果当前线程池状态在TIDYING、TERMINATED之中则不用终止因为这两个状态前面说是任务完成正在整理线程,那么既然如此就没有任务在执行了所以也直接返回
runStateAtLeast(c, TIDYING) ||
//如果当前的状态为SHUTDOWN并且workQueue不为空则直接返回因为如果是SHUTDOWN状态那么说明需要继续运行workQueue中的任务直到完成,所以这里进行了workQueue的判断。
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
//上面的if仅仅是针对线程状态的进行过滤,如果符合状态那么这里会判断当前的工作数是否不为0,如果不为0那么调用interruptIdleWorkers方法
if (workerCountOf(c) != 0) { // Eligible to terminate
//ONLY_ONE的定义是true所以这里看成true即可。
interruptIdleWorkers(ONLY_ONE);
return;
}
//获取mainLock锁
final ReentrantLock mainLock = this.mainLock;
//上锁
mainLock.lock();
try {
//如果当前的c值并没有改变则修改状态为TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//该方法是空的所以没什么可讲的,估计是给子类重写有子类自己需要中断的业务在里面。
terminated();
} finally {
//如果切换到TIDYING后说明当前锁是锁住了的所以不需要进行CAS直接复制即可。
ctl.set(ctlOf(TERMINATED, 0));
//唤醒所有termination.await等待的线程
termination.signalAll();
}
return;
}
} finally {
//解锁
mainLock.unlock();
}
// else retry on failed CAS
}
}
//onlyOne:是否值中断一个worker
//此方法并不是强力的停止线程,为什么这么说呢?在终止线程的时候需要worker尝试获取锁,这里为什么是尝试呢?因为他并不想终止线程,因为在runWorker方法中有worker.lock是直接获取当前任务的锁,从而可以看出这里仅仅是尝试去终止线程如果获取锁失败则直接跳过。
private void interruptIdleWorkers(boolean onlyOne) {
//首先获取锁因为需要对workers进行操作,而workers变量在声明的时候有说如果要操作workers就必须获取到mainLock因为他是一个HashSet所以在遍历时时不允许操作内部元素的,一旦操作就会报错,这里是避免异常,再有就是对workers的原子性同一时间只能一点在操作这样能保证数据的原子性。
final ReentrantLock mainLock = this.mainLock;
//加锁
mainLock.lock();
try {
for (Worker w : workers) {
//获取遍历中worker的线程
Thread t = w.thread;
//判断当前线程是否已经中断并且尝试上锁
//上面的锁是用来锁workers的但是workers内的worker并不是上了锁的,所以这里要操作worker所以需要尝试上个锁。
//如果当前worker线程的状态不是终止并且上锁成功了则进入该if,否则跳过。
if (!t.isInterrupted() && w.tryLock()) {
try {
//进入if后会调用线程的中断方法,进行线程的中断
t.interrupt();
} catch (SecurityException ignore) {
} finally {
//不管中断是否成功,进行解锁操作。
w.unlock();
}
}
//如果进入上方失败或者中断完成(这里的完成可能是成功也可能是失败)
//则会判断onlyOne,如果为true则直接跳出循环,否则所有任务都会进行中断操作。
if (onlyOne)
break;
}
} finally {
//解锁
mainLock.unlock();
}
}
复制代码
到这里任务的添加启动的流程已经说完了,接下来是Worker的实现。
//可以看出Woker也是一个Runnable,并且他是ThreadPoolExecutor的内部类
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable{
//worker的构造可以看出有个属性是Runnable类型的用来存储传入的firstTask
//并且调用了线程工厂创建了新的线程,newThread需要传入一个Runnable的类型所以传入了当前this。
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
//讲到这里读者需要回忆一下前面说的worker的启动。
//当时启动的线程就是构造器中创建的线程而这个线程的Runnable就是this所以在start的时候运行的下的run方法。而这个run方法有调用了,ThreadPoolExecutor的runWorker.
public void run() {
runWorker(this);
}
//启动中断
void interruptIfStarted() {
Thread t;
//需要当前的状态大于等于0并且thread不为null并且thread没有被中断
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
//则进行中断
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
//设置当前任务的状态为0
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
//在worker运行的时候调用的方法,传入的是当前正在运行的worker
final void runWorker(Worker w) {
//获取当前执行的线程
Thread wt = Thread.currentThread();
//获取worker中的Runnable属性
Runnable task = w.firstTask;
//设置 w.firstTask为null用于清空执行任务
w.firstTask = null;
//允许中断,这里的含义是根据interruptIfStarted来的unlock做了一件事就是设置state为0。unlock中调用的是tryRelease方法。
w.unlock(); // allow interrupts
//是否标记突然完成,什么叫突然完成说白了就是是否错误,如果发生了错误则completedAbruptly值不变为true如果是正常执行则为false
boolean completedAbruptly = true;
try {
//判断获取的Runnable是否为null如果不为null则代表运行当前的worker
//如果为null则获取队列中的task
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
//这个条件有点乱笔者将分开讲解
//条件1.1:runStateAtLeast(ctl.get(), STOP) 判断ctl当前的状态是否大于等于STOP
//条件1.2.1:(Thread.interrupted() ) 首先判断当前的线程是否已经被中断如果则或清除中断,并且返回true
//条件1.2.2:runStateAtLeast(ctl.get(), STOP) 与条件1.1一致
//条件2:当前线程并不是中断状态。
//下面看看条件的组合方式:
//1.ctl大于等于STOP并且当前线程你不是终止状态
//2.清除当前线程中止成功并且当前的ctl已经修改为了大于等于STOP状态并且当前线程不是中断状态
//上方两个条件满足其一即可进入if
//条件分析:条件大于等于STOP需要回顾之前讲的STOP状态的描述
//条件分析:清除中断是为了保证后面的条件能够为true并且再次判断ctl的状态是否大于等于STOP之前一直提到过这是多线程程序所以再次进行了判断。
//条件分析:不是中断状态则进行中断这个条件很容易理解,既然已经在中断那么这里就不会再进行中断了。
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
//当前线程进行中断
wt.interrupt();
try {
//空方法为了叫子类实现自己逻辑使用
beforeExecute(wt, task);
Throwable thrown = null;
try {
//执行Runnable中的run方法。
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,因为线程池需要复用线程,所以这里赋值为null而这个是一个while循环既然是null那么就会从getTask中获取任务继续执行
task = null;
//完成的任务数+1
w.completedTasks++;
//解锁
w.unlock();
}
}
//如果正常执行完则设置为false代表正常执行
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
//获取任务
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
//获取到ctl的数值在更具数值获取当前的ctl状态
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//如果状态大于SHUTDOWN 并且如果是大于STOP或者任务列表为空则工作减一返回null,则外部进行线程的销毁退出。
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//上方条件没有成立那么获取当前的worker的个数
int wc = workerCountOf(c);
// Are workers subject to culling?
//allowCoreThreadTimeOut是否允许核心线程超时默认false
//wc > corePoolSize 当前的任务数大于了设置了核心数
//合到一起则代表要么核心线程设置了超时或者当前的worker个数大于设置的核心数则timed为true,否则为false。
//这里需要注意allowCoreThreadTimeOut如果允许超时那么corePoolSize的设置将会无用,为什么这样设计呢?
//因为只要是执行到这里的都是核心线程,因超过了限定的数量后都会将Runnable存放到workerQueue中所以后面的条件并没有意义,因为一旦设置了允许超时那么创建的线程永远在getTask,获取新任务并不会销毁线程,如果不允许超时那么会判断当前正在执行的任务数是否大于设定的核心线程池数量,这是有必要的如果一旦超过那么就允许你的进行超时设置,这里是一个优化点如果正在运行的数量大于了设定的核心线程池数量说明现有的线程是无法消费需求的,既然无法达到消费需求那么自然允许你超时因为一旦不允许你超时那么当前线程是存在销毁的风险,而线程池的作用就是避免销毁和创建线程因为那样非常的消耗性能,所以这里的判断是有必要的。
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//条件1:如果当前正在执行任务数大于最大限制的线程池的数量或者timed和timedOut都为true
//条件2:当前执行的任务数大于1或者workerQueue为空
//条件1和2同时为true时进入if
//(wc > maximumPoolSize 与 workQueue.isEmpty()满足)线程池要保证ctl的准确性,ctl存储了当前工作的线程数也就是workerCount数,如果发现当前的线程数大于了最大限制的线程数并且当前workerQueue是空的则进行减一操作,并且返回null外部线程循环结束最后线程回收。如果compareAndDecrementWorkerCount操作失败说明还有人在操作worker这里进行重新循环校验。
//wc > maximumPoolSize 比较极端你只要超过我设置的最大的工作线程数那么第二条件必然是满足的因为wc>1,所以当前线程必须要回收,所以这里的 workQueue.isEmpty()完全没有意义。这里看条件不能一起看不然会绕晕的,笔者这里进行分解。
//(timed && timedOut)只要能够进入这个条件那么就代表当前的工作线程数并没有超过最大的限制,timed:需要超过核心线程数的限制或者一开始就设置了允许核心线程超时,timedOut:需要是第二次允许并且第一次并没有出错,因为初始化的时候timedOut是为false的只有经历过一次循环才会为true,这里要注意一点就是第二次循环代表着已经获取过task了并且没有获取到,那么为true就是第二次机会获取,当第二次运行的时候就发现timed的条件是成立的则判断当前的工作任务是否大于1如果等于那么在此判断当前任务队列中的任务是否为空,如果为空那么即使当前线程是最后一个线程也会进行退出销毁。这里看不明白的读者可以多看几遍因为这里需要结合上下文一起阅读,仅仅看这里是很迷糊的。
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
//将ctl值与当前的c进行比较如果没有发生改变则进行减一,执行成功则返回null外部进行线程的退出,执行失败则continue再次循环。
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//经过五关六将终于到获取这里了。
//判断是否允许超时如果允许那么使用poll方法获取,如果不允许则使用take获取。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
//获取到任务则返回任务
if (r != null)
return r;
//如果没有获取到那么重新循环并且告诉前面的五关六将这个线程已经获取过一次了,允许退出了。
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
//处理任务的退出操作,w 需要退出的worker,completedAbruptly是否异常执行
//在getTask有说过他的退出逻辑只要到这里的都是要么线程数已经最大了要么没任务可执行了,要么有任务但是现有的线程过多需要淘汰一部分的。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
//如果是异常结束,则进行减一操作,因为之前说过如果是正常退出那么在getTask中就会进行减一操作。这里算是补上了。
decrementWorkerCount();
//获取全局锁一看到他就说明我们要操作workers了
final ReentrantLock mainLock = this.mainLock;
//上锁
mainLock.lock();
try {
//将当前线程执行的所有数量统计到线程池当中
completedTaskCount += w.completedTasks;
//从workers中移除当前的工作线程
workers.remove(w);
} finally {
//解锁
mainLock.unlock();
}
//前面有介绍这个方法,做的事情就是尝试将线程池状态修改为终止状态,具体请回看
tryTerminate();
//获取ctl当前的数值
int c = ctl.get();
//之前的判断状态都是包含而这个方法是不包含STOP代表着当前的状态小于STOP,具体描述请回看
if (runStateLessThan(c, STOP)) {
//如果小于说明还有还会任务没有执行完需要继续执行
//如果当前是异常中断则不进该if
if (!completedAbruptly) {
//如果是正常退出,则判断是否设置了允许核心线程超时如果允许则返回0否则返回设置的核心线程数的大小
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//如果min为0并且队列不为空则设置min为1
if (min == 0 && ! workQueue.isEmpty())
min = 1;
//这里就有三种情况:
//1、min为0:代表队列为空所以不需要再去创建worker直接return
//2、min为1:代表队列不为空这样就要保证最少有一个工作现在继续完成任务,那么这里的判断如果当前销毁的工作线程是最后一个那么就再启动一个工作线程就是下面的if条件不满足,则当前工作的线程数小于1,但是如果min唯一说明workerQueue不为空所以必须有一个线程去执行工作。
//3、不允许当前的工作线程超时则代表只有当前的工作线程数大于了设定的核心线程数才会return,否则继续创建去执行。
if (workerCountOf(c) >= min)
return; // replacement not needed
}
//手动再次调用任务,如果任务添加成功则再次创建一个新的线程继续去完成任务否则创建失败那么就是用现有的线程去完成队列中的任务。
addWorker(null, false);
}
}
复制代码
总结状态的切换:RUNNING不用说初始化的时候就是他,SHUTDOWN在调用shutdown方法是设置为状态他,STOP在调用shutdownNow方法时设置为状态他,TIDYING在tryTerminate方法中如果任务数为0了这设置状态为他,紧接着设置状态为TERMINATED。