ThreadPoolExecutor[1] - 我们是如何提交任务的?

262 阅读3分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

线程

本质上只有一种实现

run()和start()

  • 方法级别,线程级别

  • run

    • 方法调用
  • start

    • 线程调用
    • 执行run方法
  • 有生命周期
  • 调用start方法

线程池

  • 线程复用是错误的

源码解读

线程池是dougLee在1.5引入JDK包中的。

我们依然从我们熟知的3W开始我们的旅程:

  • what's this? - 如何使用?
  • what's feature? - 有什么特征?
  • how - 如何实现上述的这些东西?

使用

我们一般都是使用自定义线程池,除了一些特定的情况下预设的几个线程池中,有符合我们需求的。

feature

实现

构造参数中的值都用来做什么?

如何维护?

线程池创建好之后,除了退出,我们使用最多的就是execute、submit方法了。

execute - 1.5

submit - 1.6

execute

我们先来看看execute方法中,都执行了什么。

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);
    }

这段代码的运行逻辑,在源码中大致解释了:

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.

但其中涉及了太多我们在阅读其他部分之前不清楚的概念,因此我们先看看是如何运行的。

先说明以下的一些变量:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
​
private volatile int corePoolSize;
​
private final BlockingQueue<Runnable> workQueue;

其中corePoolSizeworkQueue,是我们在构造方法中指定的;此处先不探究ctl是如何实现的,此处仅把这个ctl当作一个普通的AtomicInteger

该部分代码拆解
//...........
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
    if (addWorker(command, true))
        return;
    c = ctl.get();
}
//...............

这里写得很清楚,首先判断当前工作的worker数量是否小于corePoolSize。如果为true,我们就试着添加一个worker,成功了就返回,不成功就继续下面运行逻辑。

第二次c赋值ctl.get() ,也说明了一个事实:线程池中至少是部分使用了CAS部分的内容。

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);//---------问题1
}

从上一步到这一步,说明要么workerCountOf(c) <= corePoolSize,要么添加worker失败了。

那么我们要判断线程池是否是running,随后让我们的任务进入等待队列

  • 事实上在这个地方我们大概能猜测到,ctl代表着线程池的一些状态。

假设上述的都没问题(有问题就跳过了这个if,相关部分在下面) ,那么再次检查ctl,进行实际触碰到线程修改部分前的最后一次DC(double check)检查,如果这一次检查到状态发生了改变,那么就需要做补救:

  • 将我们的任务从队列中去除
  • 通过我们的拒绝策略,来进行相应的拒绝操作。
  • 这里有一个小问题,什么情况下remove会抛出false?根据代码我们知道remove失败之后就会希望加队列了。

如果最后一次检查没问题了,那么判断最后一次获取的线程池的状态中,是否我们的线程池中是否已经没有线程在跑了。

  • 如果确实是这个情况,我们就加一个worker,此时指定的参数为:(null,false)。

最后如果上述的过程中一开始的判断就无法通过,就会最后试着addWorker。

else if (!addWorker(command, false))
    reject(command);

判断的流程就是下面这个了:

www.processon.com/view/link/6…

此时我们大致清楚了是怎样的流程,但有一些问题我们还没搞明白:

  • addWorker是啥?
  • 知道了ctl代表线程池状态,那么是如何维护的?
  • 事实上remove方法中还有一个步骤
tryTerminate(); // In case SHUTDOWN and now empty

这是干啥?

addWorker
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 {
        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.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();//-------2
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

addWorker很明显的分为两个部分:

  • 检查当前线程池的状态,其中包括:

    • 线程池是否已关闭

    • 工作队列已空(当不指定firstTask时)

    • 工作线程数量已超过核心线程或最大线程池数量

      • 需要注意:此时如果判断失败了,是会直接跳出的
  • 如果通过上述的检查确定了线程池可以进行工作,那么添加一个worker

    • 注意,此处上锁了
    • 我们可以看到:实际上在这个方法中,启动线程是通过显式调用t.start()来启动的

这里引申出了新的问题:

  • 见上文的问题1,此处为什么是加入了firstTask=null?这意味着什么?

这里我们对于worker有了一个基本的认知:worker就是对于任务和线程的封装。