ThreadPoolExecutor逻辑链梳理 - 3 核心线程池与非核心线程池的添加流程

【从问题开始】

在使用线程池的时候往往会看到线程池reject相关日志,例如以下日志:

[Running, pool size = 80, active threads = 3, queued tasks = 3, completed tasks = 1674]

遂产生疑问:为什么线程池大小为80,队列长度为3的线程池,只有3个active threads就会触发reject机制?

 第一篇的结尾处还发散了另一个问题:

  • 核心线程与非核心线程的添加流程是什么?为什么会添加失败?

 本篇就此问题探究当一个任务提交到线程池后的处理流程。


目录

 addWorker方法引入


addWorker方法引入

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        
        // 流程1. 判断核心线程池是否已满,未满则执行addWorker创建核心线程
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }

        // 流程2. 判断线程池状态是否是RUNNING,阻塞队列是否能塞新的可执行对象
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 流程2.1 再判断线程池状态,非运行态,执行拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 流程2.2 运行态,如果线程池的工作Worker数量为0,创建一个空执行对象,
            // 这里是为了保证线程池至少有东西跑
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 流程3. 再次尝试addWorker创建非核心线程
        else if (!addWorker(command, false))
            reject(command);
    }

 根据前面看过的execute方法,可知传入的可执行任务Runnable是通过调用addWorker方法的返回判断是否可以执行的。进入addWorker方法。

根据addWorker的方法体,可以大致将方法分成两部分,第一部分是通过retry标签包裹的两重循环,这一部分是真正添加执行线程数量的,另一部分是retry后面的内容,这一部分是为了真正启动执行任务。

先看第一部分:

    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // Check if queue empty only if necessary.
        // 条件1、rs>shutdown
        // 条件2、排除场景(shutdown状态且传入task为null且阻塞队列不空)
        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自增低位,即在状态不变的情况下增加了线程数
            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
        }
    }

第一层循环体是通过ctl状态值判断是否可以添加worker,如果添加不了直接返回fale了。否则进入第二层循环体。这层的if判断条件是:

  • ctl的状态部分已经超过SHUTDOWN(即SHUTDOW、STOP、TIDYING、TERMINATED)
  • 排除场景:(是SHUTDOWN且没有传入的任务且任务队列非空)

第二层循环体是在判断线程池状态是可以接受新任务的前提下,判断ctl的workCount部分,对于ctl的解读,可以参考第一篇。

  • 如果workCount已经触顶,或是打算向核心线程池添加线程但核心线程池已满时,retrun false
  • 否则就可以使用CAS操作增加一个位数,即增加一个Worker数,
    • 成功增加就跳出retry部分。
    • 没有成功增加,判断状态是否和传入时一致,不一致则继续循环判断状态。

再看第二部分:

boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
    // 先根据传入的可执行任务构建Worker,并拿这个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();
                // 这里是把worker添加到workers这个set中
                workers.add(w);
                int s = workers.size();
                // 允许超出线程池最大上限,后面有缩减
                if (s > largestPoolSize)
                    largestPoolSize = s;
                workerAdded = true;
            }
        } finally {
            mainLock.unlock();
        }
        // 如果成功添加,这里执行start方法启动线程
        if (workerAdded) {
            t.start();
            workerStarted = true;
        }
    }
} finally {
    if (! workerStarted)
        addWorkerFailed(w);
}
return workerStarted;

 先根据传入的可执行任务构建Worker,并拿到对应的执行线程。这里跳转到Worker的构造函数可知,这个Worker的执行线程实际上是一个new thread。

w = new Worker(firstTask);
final Thread t = w.thread;

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

确认可以加线程执行任务时,先加锁防止同一时间线程池被ShutDown,枷锁后要重新拿ctl并判断状态。

final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {

    int rs = runStateOf(ctl.get());
    ……

state状态判断通过时,把worker添加到成员变量workers中,workers主要是用于存储线程池中的worker。

workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
    largestPoolSize = s;
workerAdded = true;

添加成功才执行start方法启动任务

if (workerAdded) {
    t.start();
    workerStarted = true;
}

不成功才执行补救方法

private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            workers.remove(w);
        decrementWorkerCount();
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}

 其实代码看到这里,可以发现在线程池里面根本就没有哪个成员变量告诉我们我存储的是核心线程,还是非核心线程。本质上核心线程和非核心线程是暴露对外的定义,而在代码内部真正执行的是加,还是不加。

可以关注到addWorker方法的参数2 boolean core。

if (wc >= CAPACITY ||
    wc >= (core ? corePoolSize : maximumPoolSize))
    return false;

实际在线程池中是没有一个成员变量标志谁是核心线程池谁是非核心线程池的。而是在addWorker方法中用一个if做判断。如果core是True即添加核心线程,是将目前的Worker数量与核心线程池大小进行比较,反之与最大线程池大小进行比较。

这里可以映射execute方法,如果比的是核心线程池,且返回false,这里addWorker必然会失败,那么execute的流程就会走到入队的流程,但在后面启动非核心线程就会成功。

本章阅读至此,基本了解了addWorker方法的机制和核心线程、非核心线程的添加。同样可以发现,在addWorker方法中对Worker的操作是十分重要的,线程启动实际上是委托给Worker完成。结合第二篇对Worker的引入,本篇同样发散以下问题:

由此我们提出以下几个问题,作为后续分析的方向:

  1. Worker是什么?它的运行机制是什么样的?
  2. 对Worker信息的统计为什么要加锁?

本篇结论:

  1. addWorker方法是将传入的Runnable对象封装成Worker,将任务启动的工作委托给Worker执行,同时通过一些列方法对Worker的状况进行统计。
  2. 线程池中没有真正意义上的核心线程池和非核心线程池的概念,实际上是通过workCount与哪一个值(corePoolSize还是maximumPoolSize)的比较,最终决定加还是不加。本质上所有的thread都是一样的。

  • 22
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值