「良心长文」Java 线程池源码详解:任务执行,worker 管理

367 阅读15分钟

前言

  这阵子看了 ThreadPoolExecutor 的源码,想着还是要总结和记录一下,就写了这篇博客。由于线程池的状态,构造函数的7个参数是什么含义blabla这种都比较简单,所以本文主要对任务执行和 worker 管理相关的源码进行了解读。作者的一得之见,如果有理解的不好的地方,希望评论区友好交流。

任务被执行的主流程

  这部分主要关注一个任务从提交到执行的主流程中涉及到的方法。

任务的提交

  这部分主要是 execute 这个函数,'execute'是执行的意思,但是任务真正的执行并不在这里,这个函数实际只起一个提交的作用,所以我称它为任务的提交,看一下它的代码:

public void execute(Runnable command) {
    // 任务为空
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // worker数量少于核心线程,直接增加核心线程执行它
    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);
}

  进来一个任务,首先会调用if (workerCountOf(c) < corePoolSize)查看当前的 worker 数量是否小于核心线程数量,如果是,直接增加核心线程去执行它;否则,调用if (isRunning(c) && workQueue.offer(command)),利用逻辑表达式的性质,检查线程池状态并将此任务加入任务队列里去,等待线程去执行它,在这个 if 语句块里,会再次检查线程池的状态,因为从上次运行int c = ctl.get();到这里已经过了一段时间,可能线程池的状态已经改变。如果线程池不是运行状态,则不允许接收新任务,要将任务从队列里踢出,if (! isRunning(recheck) && remove(command)),用这种逻辑表达式的性质可以直接保证上述功能的完成,之后再调用reject(command);执行拒绝策略;运行到else if (workerCountOf(recheck) == 0)分支,说明这段时间线程池里 worker 数量变为了0,则新增一个 worker;最后,如果进入else if (!addWorker(command, false))分支,说明:

  • 要么是添加任务到队列失败了,说明队列已满,此时在 if 判断的内部就直接进行addWorker(command, false)操作,如果这还失败,说明队列也满了,worker 也够数了,或者突然线程池状态不接收任务了,总之目前确实执行不了此任务,执行拒绝策略;如果成功,那就多加了一个 worker 去帮忙执行
  • 要么是isRunning(c)这步检查就没过,那没事,还是执行addWorker(command, false),因为在 addWorker 内部还是有检查线程池状态的代码,如果状态还是不允许接收,if 里的这个表达式返回还是 true ,还是可以执行拒绝策略的

  有人问了,为啥 worker 数量小于核心线程时,就直接新增 worker 了,不检查下线程池状态?和上面同样的道理,检查线程池状态的代码在 addWorker 里也是有的,没必要检查。

  可以看到,像 offer,remove,addWorker 这些函数,Java的设计者们给他们的返回值都是 boolean 类型,这样可以在逻辑表达式里直接判断 + 执行,简化了很多代码。

  上面提到的核心线程和非核心线程,它们有啥区别,以及新增了一个 worker 之后,又或是任务加到任务队列里,又是怎么执行任务的,下面会说。

worker 添加

  上面反复提到了 addWorker 这个函数,那么就来看看这个函数,代码很长,我们一段一段看,先看第一段代码,增加 worker 的计数。

private boolean addWorker(Runnable firstTask, boolean core) {
    // 把worker的计数 + 1
    retry:
    // 外层循环,不断得到新的 ctl,以检查线程池状态
    for (int c = ctl.get();;) {
        // 检查线程池状态
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP)
                || firstTask != null
                || workQueue.isEmpty()))
            return false;
        // 内层循环,进行计数的自增
        for (;;) {
            // 判断 worker 是否太多了
            if (workerCountOf(c)
                >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                return false;
            // CAS 方式计数自增
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            // 如果线程池状态不对,去外层循环,那里有完备的状态检查
            if (runStateAtLeast(c, SHUTDOWN))
                continue retry;
            // 走到这,说明 CAS 设置失败了,内层循环继续尝试
        }
    }
    // 下一部分代码
}

  进来首先是一个完整的状态检查,

runStateAtLeast(c, SHUTDOWN)
    && (runStateAtLeast(c, STOP)
        || firstTask != null
        || workQueue.isEmpty())

  这几种情况下,此表达式为真:

  • 线程池状态为 STOP或更高
  • 线程池状态为 SHUTDOWN,此时不接收新任务,但是 firstTask 却不为 null
  • 线程池状态为 SHUTDOWN,此时只处理任务队列的任务,但任务队列却为空

  这三种情况,显然都不应该新建 worker,所以都返回 false。

  通过了状态检查,进入了内层循环,首先是判断 worker 是否太多了;接着 CAS 自增计数,若成功,这部分代码结束,否则,再简单的重新检查下线程池状态,根据结果决定继续内层循环还是外层循环。

  这一部分代码走完了,说明线程池的 worker 计数自增成功,那么进入下一部分代码,对 worker 进行真正的新建。

private boolean addWorker(Runnable firstTask, boolean core) {
    // 上一部分代码
    // 两个标志变量
    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;
            // 加锁,因为 HashSet 并不线程安全
            mainLock.lock();
            try {
                int c = ctl.get();
                // 状态检查,合格的才能进行一系列操作
                if (isRunning(c) ||
                    (runStateLessThan(c, STOP) && firstTask == null)) {
                    if (t.getState() != Thread.State.NEW)
                        throw new IllegalThreadStateException();
                    // 加到 workers 集里面
                    workers.add(w);
                    workerAdded = true;
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                }
            } finally {
                mainLock.unlock();
            }
            // 填加 worker 成功了,t.start()去执行任务
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        // 添加 worker 失败了,进行善后操作
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

  首先是给 workers 这一 HashSet 类型加锁;接着又是检查状态,当线程池正在运行或线程池处于 SHUTDOWN 但此 worker 不是用来执行新增任务时,可以走下去;把新建的 worker 加入到 workers 里,设置 workerAdded,largestPoolSize。最后会判断是否添加 worker 成功了,若成功,调用t.start();,并设置 workerStarted,否则,进行善后操作。看一下善后操作:

private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 移除这个新建的 w,并把计数减回去
        if (w != null)
            workers.remove(w);
        decrementWorkerCount();
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}

  就是把加到 workers 集里的 worker 再移除,计数减回去。

  注意到如果添加成功,会调用t.start();进行任务的真正执行,不过要解释这个t.start();,首先要看一下 Worker 这个类。

Worker 类结构

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    private static final long serialVersionUID = 6138294804551838833L;
    @SuppressWarnings("serial") // Unlikely to be serializable
    final Thread thread;
    @SuppressWarnings("serial") // Not statically typed as Serializable
    Runnable firstTask;
    volatile long completedTasks;
    
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
    
    public void run() {
        runWorker(this);
    }
    // 一些和锁相关的方法,先省去

  Worker 类主要有 thread,firstTask 和 completedTasks三个属性:thread 是在下面的构造函数里被初始化的执行任务的线程,firstTask是初始化时传入的,可以为 null,completedTasks 统计此 worker 完成了多少任务。这里的构造函数里有一句 setState(-1);,是为了防止线程刚新建就被中断,下面会说。

  再回过头看一下上一节的t.start();,t 也就是 worker 对象里的 thread,这个 thread 是用 worker 这个实现了 Runnable 接口的类的实例来初始化,根据线程的初始化相关知识,那么t.start();也就会调用 worker 对象的 run 方法,再看 Worker 类里的 run 方法,最终调用 runWorker 方法。所以说,t.start();的效果是用线程 t 来执行 runWorker 方法,参数为 worker。

runWorker 方法

  接上节,我们分析出t.start();最终会调用 runWorker,那么来看一下这个方法。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    // 标志 worker 是否是正常退出
    boolean completedAbruptly = true;
    try {
        // 反复进行任务获取
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // 保证如果线程池是STOP状态,此线程被中断,否则,保证它不被中断
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                try {
                    // 任务的真正执行
                    task.run();
                    afterExecute(task, null);
                } catch (Throwable ex) {
                    afterExecute(task, ex);
                    throw ex;
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 善后工作
        processWorkerExit(w, completedAbruptly);
    }
}

  上来是一个w.unlock();,还记得 worker 初始化时调用了setState(-1);,这里的w.unlock();不会管 state 是否大于0,会直接调用setState(0);,这就说明这个 worker 的线程可以中断了。之后设置了 completedAbruptly,这个变量善后工作会用到;while 的循环条件为task != null || (task = getTask()) != null,而在第一个 finally 语句里,会把 task 置为 null,所以之后会反复调用 getTask 方法来获取任务,这个方法下一节说;如果可以获取到任务,首先是w.lock( ),说明这个 worker 拿到任务要去执行了,你们不要随意中断它;接着是一堆逻辑判断;之后是beforeExecute(wt, task);,在执行前做一些事情,这个函数是空函数,应该是用来被继承时重写的,然后到了task.run,任务终于被真正执行了!!!接着afterExecute(task, null);,做一些任务执行之后的事情,官方同样没有实现。在最后的 finally 语句里,将 task 置为 null,以便进行下一次的任务获取,并把上节提到的 completedTasks 自增一个。

  之后就是善后工作了,注意到,如果某次 while 条件里没有得到任务,那么 while 正常退出,此时,completedAbruptly 为 false,表示并不是突然退出,否则,如果是用户定义的任务有问题,会直接到 finally 语句里执行 processWorkerExit,completedAbruptly 为true,看一下这个 processWorkerExit 方法:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    // 尝试关闭
    tryTerminate();

    int c = ctl.get();
    // 保证线程池内线程别太少
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

  在 getTask 里,如果获取任务返回 null 时,是会进行计数减1的操作的,为了保持 worker 计数的准确性,用了completedAbruptly 来判断要不要在 processWorkerExit 显式的对 worker 的计数减一个。正常退出,不需要再减,否则,减一个。

  之后就会把此 worker 移除了,如果一个 worker 不能再得到任务,说明现在不需要这么多 worker 了,会用workers.remove(w);将它释放掉(感觉有点悲催,不需要你干活了,就把你裁了 /(ㄒoㄒ)/~~ )。

  之后调用tryTerminate();尝试关闭线程池,这有啥用后面会说;接着是一大堆代码,其实就是保证释放了 worker 之后,如果线程池还处于可执行任务的状态,它里面的 worker 数量别太少。

getTask 方法

  上面提到了 getTask 方法,很自然的,我们来看一下它。

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?
    
    for (;;) {
        int c = ctl.get();
        // 状态检查
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // 是否需要检查超时
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        // 满足回收条件,就回收 worker
        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;
            // 获取超时了
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

  上来是一个状态检查,前文提到过类似的,没什么好说;主要看这个是否超时的代码:先是计算 timed,boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;;之后如果满足条件if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())),则获取失败,根据上节内容,此 worker 会被回收。

  仔细分析if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty()))语句,如果 &&的前半部分满足了,后半部分语句(wc > 1 || workQueue.isEmpty())的语义其实就是只要 worker 数量大于1,全都释放掉;即使 worker 数量为1,但是任务队列为空,也释放掉它,可以说是非常狠了。也就是说,前半部分语句实际上是控制要不要进行 worker 回收的关键。

  要解释前部部分语句,就要看一下 timed 以及 timeOut。在条件语句中,它俩是一块出现的:(timed && timedOut),也就是说,只有 timed 为 true 时,timeOut 变量才有意义。boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;,当 allowCoreThreadTimeOut 为真,或者当前 worker 数量大于 corePoolSize 时,此变量为真。allowCoreThreadTimeOut 是手动设置的:

public void allowCoreThreadTimeOut(boolean value) {
    if (value && keepAliveTime <= 0)
        throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
    if (value != allowCoreThreadTimeOut) {
        // 进行设置
        allowCoreThreadTimeOut = value;
        if (value)
            interruptIdleWorkers();
    }
}

  看一下 timeOut 何时为真,它的设置在 try 语句块里:

Runnable r = timed ?
   workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
   workQueue.take();
if (r != null)
   return r;
// 获取超时了
timedOut = true;

  上面说了,timed 是是否要检查超时,这个代码的意思就是如果需要检查超时,那就调用任务队列的超时获取方法去获取任务,否则,调用不带超时的方法。总之这个 worker 在限定的时间范围内没获取到任务,timeOut 就是 true。这也说明了只要 worker 数量大于 corePoolSize,那获取任务都不能无限等待,即使 worker 数量小于 corePoolSize,也可以手动调用 allowCoreThreadTimeOut 来设置 timed 为 true。

  总结下,以下几种情况,(wc > maximumPoolSize || (timed && timedOut)为真,需要释放 worker:

  • 目前 worker 数量大于 maximumPoolSize,这个 worker 都不该存在,自然不能获取成功
  • 目前 worker 数量大于 corePoolSize,当前的 worker 上一次获取任务失败了
  • 目前 worker 数量小于等于 corePoolSize,但手动设置了 allowCoreThreadTimeOut 为true,并且当前 worker 上一次获取任务失败了

  也可以看出,如果没手动设置 allowCoreThreadTimeOut 为true,只要 worker 数量小于等于 corePoolSize,即使获取任务失败了,也不会被释放,因为 timed 肯定是false,这个变量可以对 worker 数量进行把控。并且每个 worker 并没有标志位来标志谁创建时是核心的,线程池只关心数量,最后剩下的 worker 未必开始就是以核心模式创建的。

小结

  从 execute 一步步走到这,也可以概括下任务如何被成功执行的了,一个任务被执行,那就是以下几种情况:

  • 提交时,worker 数量小于 corePoolSize,创建 worker 执行,作为此 worker 的 firstTask 被执行
  • 提交时,worker 数量大于等于 corePoolSize,任务队列没满,进入任务队列,等待 worker 之后调用 getTask 从任务队列里 poll 或 take 出来
  • 提交时,worker 数量大于等于 corePoolSize,任务队列满了,worker 数量小于 maximumPoolSize,创建 worker 执行之

  以上三种情况都是建立在线程池状态没问题的前提下,线程池状态如果中间出了问题,都会执行失败。

worker 的管理

  worker 的结构,新建,运行,获取任务在上节已经说过,本节主要关注 worker 的释放。

未调用 shutdown,shutdownNow

  在上节的 getTask 方法里已经总结了,可以向上翻阅。

调用 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();
}

  首先调用了advanceRunState(SHUTDOWN);来设置线程池状态:

private void advanceRunState(int targetState) {
    // assert targetState == SHUTDOWN || targetState == STOP;
    for (;;) {
        int c = ctl.get();
        if (runStateAtLeast(c, targetState) ||
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break;
    }
}

  方法也很简单,就是一直 CAS 设置状态到成功为止,主要看interruptIdleWorkers();

private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}  
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            // 判断 worker 是不是空闲
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

  怎么判断一个 worker 是否 idle 呢?主要是这个判断:if (!t.isInterrupted() && w.tryLock()),如果对应的线程已经被中断了,那最好,否则,会调用w.tryLock()来尝试获取锁。前面的 worker 结构里,这个地方跳过了,回过头看一下:

// Worker 类内部
public void lock()        { acquire(1); }
public boolean tryLock()  { return tryAcquire(1); }
protected boolean tryAcquire(int unused) {
    if (compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}

  Worker 类是继承了 AQS 类的,内部也重写了锁相关的方法,调用 tryLock 方法会尝试进行compareAndSetState(0, 1),而前面的 runWorker 方法中,运行一个任务前,会调用w.lock();语句,所以如果一个 worker 在执行任务,interruptIdleWorkers 是无法成功的。判断是空闲 worker 之后,会把它对应的线程设置中断状态。

  对线程有点基础知识就会知道,给一个线程设置中断状态并不会结束这个线程,如何释放 worker 还是要看逻辑代码里是如何处理这些被中断的线程的。可以分为下面几种情况:

  • 调用 shutdown 时,线程刚好完成了任务,线程中断位被标记,这时候由于 runWorker 里的 while 循环,线程会去 getTask 里取任务,getTask 返回为 null 是以下两种情况:
// 条件1
if (runStateAtLeast(c, SHUTDOWN)
    && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
    decrementWorkerCount();
    return null;
}  
// 条件2
if ((wc > maximumPoolSize || (timed && timedOut))
    && (wc > 1 || workQueue.isEmpty())) {
    if (compareAndDecrementWorkerCount(c))
        return null;

  走到 1 时,由于已经设置了线程池状态为 shutdown,只需要判断workQueue.isEmpty()条件,只要任务队列不空,这个条件还是可以过,这也符合 shutdown 状态的描述,把任务队列里的任务做完。此 worker 会接着获取;2 其实是控制 worker 数量的,和我们说的 shutdown 方式释放 worker 关系不大。这个 worker 去任务队列接着获取:

public void lockInterruptibly() throws InterruptedException {
    sync.lockInterruptibly();
}
final void lockInterruptibly() throws InterruptedException {
    // 检查中断位并重置
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!initialTryLock())
        acquireInterruptibly(1);
}

  不管是 poll 还是 take 方法,调用的是 lockInterruptibly 方法,这个方法会调用if (Thread.interrupted()),这个Thread.interrupted()是会重置中断位的,所以此次获取,会抛出异常,由于 getTask 这块有catch (InterruptedException retry)语句,其实还会循环获取任务,下一次获取由于中断位被重置了,还是可以正常获取,直到任务队列为空,或者不满足 2 中的数量要求,此 worker 会被释放。

  • 调用 shutdown 时,线程正在阻塞获取任务,抛出中断异常,由于有 catch 语句,和上面那种情况一样,还是会一直获取任务,直至任务队列空了或者不满足 2 中条件,被释放。
  • 调用 shutdown 时,线程正在执行任务,那么它不会被标记中断,等它做完了任务,再正常获取任务即可,直至任务队列空了或者不满足 2 中条件,被释放。

  可见,调用了 shutdown,也不会立刻释放所有 worker,还是得把任务队列的任务都完成了。

调用 shutdownNow

  线程池还提供了 shutdownNow 方法对线程池状态进行管理:

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 设置线程池状态为 stop
        advanceRunState(STOP);
        // 中断所有 worker
        interruptWorkers();
        // 获取任务列表
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

  首先设置线程池状态,这很简单;接着调用interruptWorkers();

private void interruptWorkers() {
    for (Worker w : workers)
        w.interruptIfStarted();
}  
void interruptIfStarted() {
    Thread t;
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
        try {
            t.interrupt();
        } catch (SecurityException ignore) {
        }
    }
}

  代码很简单,只要不是新建状态的 worker(worker 构造函数里有一个setState(-1);语句,这种的 worker 确实无法中断,不过它既然被新建出来了,下一步肯定要 runWorker 的),管他在不在执行任务,都给它的线程进行中断。再看上节的条件1:if (runStateAtLeast(c, SHUTDOWN) && (runStateAtLeast(c, STOP) || workQueue.isEmpty())),由于调用了advanceRunState(STOP);,这个条件肯定是真了,走不到判断任务队列空不空了。只有在执行任务的 worker 可以逃过一劫,其它的都过不了这个条件,从而 getTask 返回个 null,在 runWorker 里被 processWorkerExit 释放掉;执行任务的 worker 执行完了,也会在这个条件里被释放。

worker 会不会释放不完?

  有一个疑问,比方说某时刻,线程池 corePoolSize 为5,allowCoreThreadTimeOut 为 false,任务队列还有1个任务,线程池还有5个线程,他们此刻都恰好刚刚完成了任务准备去再次获取任务,这时候调用了 shutdown 或者 shutdownNow,会不会出现它们5个线程同时进入了 getTask,同时通过了条件1和条件2,同时 workQueue.take();,同时抛出异常又再次同时通过条件。总之,最后是1个线程获取到任务,剩下4个在 workQueue.take(); 处阻塞了,由于 shutdown 和 shutdownNow 也只会中断一次,那4个线程永远释放不了?这其实是不会的,看一下 processWorkerExit 里的 tryTerminate:

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        // 状态不满足
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateLessThan(c, STOP) && ! workQueue.isEmpty()))
            return;
        // 唤醒下一个空闲线程
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }
        
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 线程池状态设置为 TIDYING
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                    // 线程池状态设置为 TERMINATED
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

  可以看到,即使出现上面说的那种情况,某 worker 被释放了之后,就会调用interruptIdleWorkers(ONLY_ONE);唤醒下一个空闲线程,下一个再唤醒下下一个,所有在workQueue.take();处阻塞的 worker 最终都会被唤醒,过不了条件1,被释放。

  也可以看到,当 worker 数量为0时,tryTerminate 会设置线程池状态为 TIDYING,继而设置为 TERMINATED,这时候,线程池才真正关了,才调用termination.signalAll();提醒那些等待线程池被关的线程。

总结

  本文主要对线程池里任务的执行和 worker 的管理相关的源码进行了梳理,由于是个人写文,没有时间画流程图,可以去美团这篇技术博客 Java线程池实现原理及其在美团业务中的实践 看看,这篇博客画了非常易懂的线程池各个流程的流程图。