在直接看源码之前建议先看上一篇文章:线程池ThreadPoolExecutor原理
public class ThreadPoolExecutor extends AbstractExecutorService {
// ctl这个变量在源码中很常见。它是代表了当前线程池的状态以及线程的数量
// 高3位表示线程池状态 剩下的29位表示线程数量
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;
// 线程池的五种状态
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;
// 获取当前线程池的状态 取高三位
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 获取当前线程池中的线程数量,直接取低29位
private static int workerCountOf(int c) { return c & CAPACITY; }
// 传入线程池状态与线程池数量,合并返回完整的ctl变量
private static int ctlOf(int rs, int wc) { return rs | wc; }
// 判断当前是否是运行状态
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
......
}
所以线程池的五种状态表示如下:
假如ctl为:11100000 00000000 00000000 00001010,就表示线程池的状态为RUNNING,线程池中目前在工作的线程有10个
接下来详细分析线程池中的核心方法
执行一个任务就需要考虑是否需要创建线程,还是直接进入阻塞队列中。
public void execute(Runnable command) {
// 如果传入的对象为null 直接就抛空指针异常
if (command == null)
throw new NullPointerException();
// 当前线程是否未达到核心线程数,如果小于则调用addWorker()去创建新线程执行任务
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 当前线程池状态为运行,再去尝试入队操作
if (isRunning(c) && workQueue.offer(command)) {
// 线程入队后可能线程池就不是运行状态了,需要重新查询一次,如果不是那么就要从队列中移除当前任务,并且触发拒绝策略
// 为了确保刚刚入队的任务有线程去处理,就需要判断一下线程数量,如果为0那么就调用addWorker()方法区创建一个线程
// 添加的这个线程没有自己的任务,为了就是从队列中去获取
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果阻塞队列满了 入队失败 则调用addWorker()方法创新新线程,如果达到了最大线程数那么就触发拒绝策略
else if (!addWorker(command, false))
reject(command);
}
addWorker()
方法是核心方法,是用来添加线程的。core
形参变量用来标识是不是核心线程,
我们在添加一个线程前,需要判断线程数是否操作核心线程数或者是最大线程数,如果超过了则不允许添加线程。
并且在addWorker()
方法中还需要判断线程池的状态,如果不是RUNNING状态了那也就没必要再去添加线程了。但线程池的状态是SHUTDOWN,队列中有任务,那此时还是需要添加添加一个线程的,原因如下:
可能存在线程池中的线程都被回收了,在线程池中有这么一个参数:allowCoreThreadTimeOut,表示是否允许核心工作线程超时,默认值是false。
private boolean addWorker(Runnable firstTask, boolean core) {
// 这个for循环就是用来判断有没有必要去添加线程
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 这里就是判断线程池状态,以及阻塞队列是否为null。因为firstTask == null的情况是上面源码中任务入队成功之后才会出现
// 既然线程池是SHUTDOWN,阻塞队列刚刚入队的任务又被其他线程消费了,队列已经空了,那么也就没有必要再去创建线程了
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;
// 上面两个条件都满足后就去CAS尝试修改线程数 进行自增。万一CAS失败了就再走一遍循环
if (compareAndIncrementWorkerCount(c))
break retry;
// 再获取一遍线程状态,如果被改变了那么就重新走一遍外循环,如果线程状态没改变就重新走一遍内循环
c = ctl.get();
if (runStateOf(c) != rs)
continue retry;
}
}
// 接下来就是真正添加线程的逻辑
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 创建一个线程
// 把当前任务封装为一个Worker对象,Worker实现了Runnable接口,相当于就是创建了一个线程
w = new Worker(firstTask);
// 拿出线程对象,还没有start
final Thread t = w.thread;
if (t != null) {
// 加一把可重入锁,
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
// 如果线程池状态是RUNNING或SHUTDOWN,但firstTask == null就表示这是要创建一个线程从队列中获取任务来执行
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 如果Worker对象对应的线程已经在运行了,那就有问题,直接抛异常
if (t.isAlive())
throw new IllegalThreadStateException();
// workers用来记录当前线程池中工作线程,shutdown()方法时会遍历它进行中断线程
workers.add(w);
// largestPoolSize用来跟踪线程池在运行过程中工作线程数的峰值
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 启动线程,去执行任务
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
// 如果上面代码出现了异常,需要从works中移除所添加的work,并且还要修改ctl,工作线程数-1,表示新建工作线程失败
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Worker类它有两个属性
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
private static final long serialVersionUID = 6138294804551838833L;
// 上面addWorker()方法中获取的就是这个属性的值 表示Worker对应的线程,就是这个线程来获取队列中的任务并执行的
final Thread thread;
// 下面这个firstTask属性就是我们自定义要执行的任务 Worker待执行的第一个任务,第二个任务会从阻塞队列中获取
Runnable firstTask;
// 记录当前Work共执行了多少次任务
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // AQS的state
this.firstTask = firstTask; // 把我们真正要执行的第一个任务赋值给了firstTask属性
this.thread = getThreadFactory().newThread(this); // 这里创建了一个新线程,传参是this 也就是当前对象实例
}
// 所以addWorker()方法中启动线程,实际上会调用这个run()方法
public void run() {
runWorker(this);
}
...
}
在addWoker()
方法中启动了线程,就会运行run()
方法,而最后就会调用runWorker(this)
方法
指定任务的方法。
首先执行Worker类中的firstTask属性指向的任务,然后在循环从阻塞队列中获取任务,再去执行任务。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 将最新添加的任务去出来
Runnable task = w.firstTask;
w.firstTask = null;
// 这个地方为什么要先解锁,这是因为要将state从-1改为0,允许线程中断/加锁
w.unlock();
boolean completedAbruptly = true;
try {
// 如果task为null,那么就直接去阻塞队列中获取任务。getTask()方法可能返回task、可能返回null、可能阻塞。后续详细分析
while (task != null || (task = getTask()) != null) {
// 先加锁,和shutdown()方法有关,shutdown()方法中断线程时会来竞争锁
w.lock();
// 如果当前线程池状态是STOP,正常来说该工作线程的中断标记为应该是true,如果是false,则需要中断自己
// 如果线程不为STOP,要么是RUNNING、要么是SHUTDOWN。如果发现当前中断位为true,那是不对的,因为工作线程还需要处理队列中的任务
// 所以就要Thread.interrupted()重置中断标识位,重置标识位之后再一次判断线程池状态,如果忽然变为了STOP那么就又重新中断自己
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 空方法,给自定义线程池来实现
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 运行任务
// 可能成功,运行完后就回到while循环继续从阻塞队列中拿任务;可能出异常,进入finally逻辑。
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;
// 记录当前Work共执行了多少次任务
w.completedTasks++;
w.unlock();
}
}
// 如果出异常 这行代码也不会执行,值还是true;如果是线程空闲等待超时跳出的while循环那么值就变为了false
completedAbruptly = false;
} finally {
// 如果工作线程执行某个任务出现了异常,那么就会跳出while循环,调用下面这个方法,此时completedAbruptly == true
processWorkerExit(w, completedAbruptly);
}
}
当某个工作线程执行某个任务出现异常后,那么当前这个工作线程就会淘汰掉,并重新创建一个线程
如果是最后一个线程退出,那么就要修改线程池状态为TINDYING
如果线程是正常退出,也就是completedAbruptly == false
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 线程异常退出,首先把工作线程数自减;
// 线程正常退出的情况下是在getTask()方法中已经自减然后在退出,所以线程正常退出情况下不需要执行下面方法
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 当前Work要运行结束了,将完成的任务数累加到线程池上
completedTaskCount += w.completedTasks;
// 从集合中移除当前work
workers.remove(w);
} finally {
mainLock.unlock();
}
// 线程退出逻辑,要去尝试修改线程池状态TINDYING
tryTerminate();
int c = ctl.get();
// 线程状态为RUNNING 或 SHUTDOWN状态就会进入下面的if
if (runStateLessThan(c, STOP)) {
// 如果线程是正常退出就执行下面if
if (!completedAbruptly) {
// allowCoreThreadTimeOut核心线程是否超时,默认值false
// 如果allowCoreThreadTimeOut为true,并且阻塞队列不为null,那么就至少需要一个线程去处理任务
// 如果allowCoreThreadTimeOut为false,那就至少得保留corePoolSize个工作线程活着
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
// 当前工作线程数是否达到了最小存活线程数,如果达到了就直接返回
if (workerCountOf(c) >= min)
return;
}
// 如果线程是异常退出就要在添加一个工作线程;线程正常退出但是最小存活线程数条件没满足也需要再添加一个工作线程
addWorker(null, false);
}
}
总结:
线程淘汰退出有三种情况:非核心线程超过最大空闲时间、线程执行任务出了异常、线程池掉了shutdown()方法中断了工作线程
所以processWorkerExit()方法中就做一些善后工作
从阻塞队列中获取任务。
此方法会把超过了最大空闲时间的非核心线程数淘汰
当线程池状态为SHUTDOWN时,如果阻塞队列中的任务执行完了那么也淘汰所有线程,直接返回null就是淘汰退出线程,因为runWorker()
方法的循环条件就不满足了,进而导致线程run()方法也就执行完,线程也就退出了。
当线程池状态为STOP时,直接淘汰所有线程
private Runnable getTask() {
// 线程是否超时标记
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 检查线程状态,淘汰线程,直接返回null也就是上面提到了第二、第三点
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// 工作线程数-1
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// 如果allowCoreThreadTimeOut为true,那么timed就直接为true
// 否则timed就是判断当前工作线程是否大于了最大核心线程
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 如果工作线程数大于了最大线程数 或者是 线程超时标记timedOut与timed都为true
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
// CAS机制减少工作线程数,CAS成功则返回null,同一时刻只有一个线程会退出,其他CAS失败的线程再走一遍循环
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 从阻塞队列中获取任务
// 大部分情况下都是非核心线程数超时阻塞。核心线程数永久阻塞
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
// 如果拿到任务就直接返回任务,如果因为超时时间到了而没有拿到任务就要把超时标记timedOut = true,然后再去走一遍循环
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
// 线程阻塞过程中,线程池调用了shutdown()方法 会中断,这不属于超时,会重新走循环
// 线程池的状态变成了STOP或者SHUTDOWN,最终也还是会return null,
// 但是如果线程池的状态仍然是RUNNING,那当前线程会继续从队列中去获取任务,表示忽略了本次中断
timedOut = false;
}
}
}
所以 只有通过调用线程池的shutdown方法或shutdownNow方法才能真正中断线程池中的线程。
调用线程池的shutdown方法,表示要关闭线程池,不接受新任务,但是要把阻塞队列中剩余的任务执行完。
shutdown方法要做的第一件事情就是修改线程池状态。
第二件事情就是要中断线程池中的工作线程,这些工作线程要么在执行任务,要么在阻塞等待任务:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 修改线程池状态
advanceRunState(SHUTDOWN);
// 中断工作线程
interruptIdleWorkers();
// 空方法,给子类扩展使用
onShutdown();
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 遍历workers集合,
for (Worker w : workers) {
Thread t = w.thread;
// 如果线程没有被中断,并且能够拿到锁,就中断线程
// Worker在执行任务时会先加锁,执行完任务之后会释放锁
// 所以只要这里拿到了锁,就表示线程空出来了,可以中断了
if (!t.isInterrupted() && w.tryLock()) {
try {
// 中断每个线程
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
// 上面传的false,不会执行
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
调用shutdown()方法中断所有线程后,队列中剩余的任务怎么办呢?
队列中如果没有任务了,中断线程也就不会有什么影响,线程正常中断退出,此时线程池状态是SHUTDOWN,也不会有新任务进来
队列中如果还有任务,调用shutdown()方法后当前工作线程还是会继续执行队列中的剩余任务。
就算是出什么问题,在runWorker()
方法中的finally
语句段中还会调用processWorkerExit()
方法,在这个方法中会对线程池状态为SHUTDOWN进行判断,如果不满足了最小核心线程数 会重新生成新的工作线程,那么这样就能保证队列中剩余的任务一定会被执行完。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 修改线程状态
advanceRunState(STOP);
// 中断线程
interruptWorkers();
// 返回阻塞队列中剩余的任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
// 调用terminated方法
tryTerminate();
return tasks;
}
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
// 中断所有工作线程,不管有没有在执行任务
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
void interruptIfStarted() {
Thread t;
// 只要线程没有被中断,那就中断线程,中断的线程虽然也会进入processWorkerExit方法,但是该方法中判断了线程池的状态
// 线程池状态为STOP的情况下,不会再开启新的工作线程了
// 这里getState>= 0表示,一个工作线程在创建好,但是还没运行时,这时state为-1,可以看看Worker的构造方法就知道了
// 表示一个工作线程还没开始运行,不能被中断,就算中断也没意义,都还没运行
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
在上述源码中,发现很多地方都会用到mainLock,它是线程池中的一把全局锁,主要是用来控制workers集合的并发安全,因为如果没有这把全局锁,就有可能多个线程公用同一个线程池对象,如果一个线程在向线程池提交任务,一个线程在shutdown线程池,如果不做并发控制,那就有可能线程池shutdown了,但是还有工作线程没有被中断,如果1个线程在shutdown,99个线程在提交任务,那么最终就可能导致线程池关闭了,但是线程池中的很多线程都没有停止,仍然在运行,这肯定是不行,所以需要这把全局锁来对workers集合的操作进行并发安全控制。