线程池源码阅读

145 阅读7分钟

虽然看过线程池的代码, 但是实际工作中都是用的工具类, 很少实际真正意义上用过线程池. 这次有个需求要对线程池做一点改动, 要改动, 肯定得非常熟悉线程池的工作流程.

假设线程池正常工作的场景下阅读线程池的工作流程

一、ThreadPoolExecutor

  首先分析构造函数中参数的含义, 如果搞明白这些, 其实就不需要死记Executors工具类中例如fixed, schedule这种提供的线程池的作用了.

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler);
  • corePoolSize: 线程池中核心线程数量
  • maximumPoolSize: 线程池中最大线程数量
  • keepAliveTime:
  • workQueue: 任务队列

  到目前为止一脸懵逼, 一定要结合线程池的工作流程来分析这几个参数.

二、execute提交任务

2.1 execute
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 1.threads < corePoolSize, 创建线程执行该任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
           return;
        c = ctl.get();
    }
    // 2.threads >= corePoolSize时, 线程先入队
    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);
    }
    // 3.线程创建失败, 拒绝该Task
    else if (!addWorker(command, false))
        reject(command);
}

这里涉及三个步骤:

1、如果线程池中线程数量threadSize < 核心线程corePoolSize, 则对该command进行addWorker处理

2、如果threadSize < corePoolSize且addWorker处理该Task失败 或者 threadSize > corePoolSize, 此时进行workQueue.offer即任务入队操作, 入队成功, 判断当前线程池中线程数量, 如果为0再次尝试执行addWorker操作.

3、第二步中如果入队失败, 对该task进行addWorker处理.

三次addWorker,分别传入的参数是(command, true), (null, false), (command, false).

2.2 addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        for (;;) {
            int wc = workerCountOf(c);
            /**
             * 1.结合execute中调用addWorker的三种场景, 只有第一种场景threadSize < corePoolSize时,
             * 调用addWorker传入的参数为true.
             * 2.所以结合这里当core为true时, wc < corePoolSize.
             * 3.core = false, 且wc >= maximumPoolSize的场景:
             *   任务队列满了之后, Task进入不了workQueue, 此时会创建非核心线程执行task, 直到
             *   wc >= maximumPoolSize.
             */
            if (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
        }
    }
    /**
     * 结合上面的for循环, 如果能执行到这里, 有两种情况:
     * 1.core为true且wc < corePoolSize;
     * 2.core为false且wc < maximumPoolSize;
     */
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        /**
         * 结合execute中的三种场景, 只有第二种场景传入的firstTask为null, 满足这种场景的前提是:
         * 1. wc > corePoolSize;
         * 2. workQueue.offer(firstTask)为true, 也就是说任务队列未满
         * 此时构造Worker时传入了firstTask = null, 考虑一个问题, Worker如何执行firstTask?
         */
        // 到这里可以知道Worker与Thread是1:1的关系
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                workers.add(w);
                int s = workers.size();
                if (s > largestPoolSize)
                    largestPoolSize = s;
                // Worker能否执行的标识位
                workerAdded = true;
            } finally {
                mainLock.unlock();
            }
            // 如果Worker能够被执行, 也就是被添加到workers中, 执行该worker
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

分析到这里, 大致明白addWorker在什么场景下会返回true/false

1、addWorker = true: (1)core = true且wc < corePoolSize, (2)core = false且wc < maximumPoolSize;

2、addWorker = false: core = false且wc > maximumPoolSize;

接下来分析执行线程的流程

三、任务被执行的流程

3.1 runWorker
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        /**
         * 1.如果task不为空, 直接调用task.run()
         * 2.如果task为空, 也就是execute()中的场景二, 通过getTask()获取该task,
         *   然后调用task.run()
         */
        while (task != null || (task = getTask()) != null) {
            w.lock();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 执行完之后释放worker.
        processWorkerExit(w, completedAbruptly);
    }
}

1、结合execute方法中调用addWorker()的三种场景, 如果传入的参数是(task, true)、(task,false)时, addWorker中构建Worker时会将该task传入, 然后在runWorker中task != null, 直接调用该task.run()

2、传入(null, true)时, 此时首先需要将task进行入队, execute中的workQueue.offer操作, 然后runWorker中再通过getTask()获取该task, 然后执行task.run()

3.2 getTask()获取任务
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // Check if queue empty only if necessary.
        int wc = workerCountOf(c);
        // allowCoreThreadTimeOut: 是否允许核心线程超时
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        try {
            // 这里采用了AQS锁以及相关集合的特性:
            // 1.poll(timeParams)阻塞当前线程timeParams时间
            // 2.take()会一直阻塞当前线程, 直到workQueue中有数据进来.
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

1、这里涉及到了AQS锁的特性, 如果timed = true允许超时, 则调用workQueue.poll将当前线程阻塞 keepAliveTime 时间, 然后再唤醒该线程, 而keepAliveTime与TimeUnit.NANOSECONDS就是ThreadPoolExecutor构造函数中传入的两个参数

2、如果timed = false, 也就是不允许超时, 则当前线程会被一直阻塞.

3、timed与allowCoreThreadTimeOut、wc > corePoolSize两个因素有关, 第一个参数表示是否允许核心线程超时, 第二个条件表示当前线程池中的线程数量是否大于核心线程数量, 这两个参数结合起来也就是(1)如果允许核心线程超时, 且队列为空时, 核心线程挂起keepAliveTime时间, 然后销毁.(2)非核心线程且队列为空时, 挂在keepAliveTime时间, 然后停止运行.

4、allowCoreThreadTimeOut涉及到allowCoreThreadTimeOut(boolean value)方法, 线程池提供接口运行自定义是否允许核心线程超时, 默认情况下, allowCoreThreadTimeOut = false, 既不允许核心线程超时.

四、考虑以下几个问题:

1、如果线程池中的线程数量 < corePoolSize, 且线程处于挂起状态, 此时如果有新任务时, 线程池是如何处理?

2、如果线程池中线程数量 > corePoolSize时, 此时如何处理新来的任务?

4.1 针对问题一

结合execute与addWorker其实可以知道, 如果workers < corePoolSize时, 流程中并没有对挂起的核心线程进行唤醒操作, 而是通过addWorker直接创建线程操作, 也就是说, 只要线程池中的workers < corePoolSize, 新来任务时, 就会创建线程执行该Task.

4.2 针对问题二

1、当workers > corePoolSize时, 通过execute添加新的task时, 会首先将该task添加到workQueue中, 如果添加成功, 表明任务队列未满, 此时根据AQS锁的机制, 会唤醒调用getTask()被阻塞的线程, 线程被唤醒之后, 从队列中获取task并执行, 从而达到线程复用的目的.

2、但是到这里又有一个疑问了, 先贴出execute中的第二种场景

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

执行到这里, 说明当前线程池中 threads >= corePoolSize, 此时只知道线程池中线程的数量关系, 线程的状态(运行, 挂起)不可知, 这个其实不用太关心, 只需要知道workQueue.offer将task添加到任务队列, 是为了唤醒在getTask()处挂起的线程, 让线程能够复用. 既然是复用, 那为何还要再次判断workerCountOf(rechcke) == 0操作呢? 如果为true, 则调用addWorker, 如果线程池正常运行, 且 threads < maximumPoolSize 则addWorker是一定会创建一个线程的.

网上看到很多文章介绍到这里都是说, 通过workerCountOf(rechcke) == 0判断如果为true, 表示没有线程, 然后通过addWorker创建线程执行该task, 那有没有这种情况, workQueue.offer唤醒了挂起的线程执行该Task, 然后在workerCountOf(recheck) == 0之前, 当前线程执行完task并且销毁, 此时workerCountOf(recheck) == 0为true, 而workerQueue并没有task. 既然这样, addWorker的意义何在?

这里先记录留下问题, 个人理解也不知道对不对, 以后如果有能力再矫正这里的观点, 此时如果workerCount == 0为true, 也就是当前线程池中并没有可用线程, 此时不管workQueue是否为空, 都调用addWorker创建一个新的线程, 如果workQueue不为空, 则从workQueue中取任务执行, 如果workQueue为空, 则预创建一个线程以待执行下次execute提交的task.