ThreadPoolExecutor

线程池,工作中用的非常多的一个类。当线程池化,我们都知道线程的开启与销毁很消耗性能,所以在一些框架中都将线程进行池化,比如Redis连接池、数据库连接池等等。线程池的核心思想是任务的提交与执行区分开

基本属性

线程池有哪些状态?线程池的线程数和状态是如何表示的?这些都可以从类的成员变量体现出来

// 用来记录线程池的状态和线程数量,一个变量表示两个,做法就和读写锁一样了,将该变量按位拆,高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;

// 线程池状态:RUNNING SHUTDOWN STOP TIDYING TERMINATED
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 final BlockingQueue<Runnable> workQueue;
// 改变线程池信息时,比如新增线程、停止线程或者访问线程池共享参数信息等需要进行加锁,
private final ReentrantLock mainLock = new ReentrantLock();
// 工作线程的set集合,只有在持有mainLock锁的时候才能访问
private final HashSet<Worker> workers = new HashSet<Worker>();
// 主要是为了支持awaitTermination方法,当外部线程调用awaitTermination方法时,判断线程池的状态是否是TERMINATED状态,如果不是就将外部线程加入到termination条件队列中,直至超时或者线程执行完毕
private final Condition termination = mainLock.newCondition();

// 目前为止线程池中的拥有的最大线程数量
private int largestPoolSize;
// 已完成任务的数量
private long completedTaskCount;
// 线程工厂,创建线程池时可以使用线程池创建线程,给特定任务取特定的线程名
private volatile ThreadFactory threadFactory;
// 拒绝策略,线程默认提供了四个拒绝策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy
private volatile RejectedExecutionHandler handler;
// 存活时间,即在allowCoreThreadTimeOut为false时,除了核心线程之外,其余线程的空闲时间
private volatile long keepAliveTime;
// 是否允许核心线程超时,如果允许,那么在keepAliveTIme时间后还是空闲则核心线程也将关闭
private volatile boolean allowCoreThreadTimeOut;
// 核心线程数量
private volatile int corePoolSize;
// 线程池的最大数量,不可超过CAPACITY
private volatile int maximumPoolSize;

构造方法的七个参数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

从这个最全的构造方法中说下七个参数:

  • corePoolSize:核心线程数,最先使用的线程
  • maximumPoolSize:最大线程数,当阻塞队列是无界队列时,该参数无效
  • keepAliveTime:存活时间,除了核心线程之外的线程在空闲状态下存活的时间
  • unit:存活的时间单位
  • workQueue:阻塞队列
  • threadFactory:线程工厂,线程池的线程由线程工厂创建,在创建的时候可以给该线程池的线程取一个特殊的名字,便于查找
  • handler:拒绝策略,当线程池中的线程数达到了maximumPoolSize数量并且外部线程再次提交任务时执行该策略

线程池状态

线程池有这么几个状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED,那么这几个状态分别代表的含义以及状态的流转如下:

状态 说明 转换
RUNNING 线程池运行状态,该状态下可以接收新任务以及对任务的处理 线程池的初始状态
SHTUDOWN 当线程池处于该状态下,不再接收新任务,但会将池中的任务处理完毕 RUNNING状态下调用了shutDown方法
STOP 当线程池处于该状态下,不再接收新任务,并且池中的任务也会停止,正在进行的任务也会终止掉 RUNNING或者SHUTDOWN状态下调用了shutDownNow方法
TIDYING 当线程池处于该状态下,所有的任务已执行完毕或者别丢弃,接着会调用terminated()方法 SHUTDOWN状态的线程池,当任务队列为空或者工作线程为0会自动转换为TIDYING状态;STOP状态下的线程池,当工作线程数为0会自动转换为TIDYING
TERMINATED 执行完terminated方法,状态变为TERMINATED,线程池彻底终止 TIDYING状态下的线程池调用terminated方法

阻塞队列

当线程池中的工作线程数达到了核心线程数corePoolSize时,再有新任务时,会将新任务添加到阻塞队列中,阻塞队列有这么几种:有界阻塞队列、无界阻塞队列、延迟队列等,其中使用的最多的是有界阻塞队列,因为使用无界阻塞队列不小心可能会发生OOM的情况

拒绝策略

当线程池中的线程数超过了maximumPoolSize时,再有新的任务提交进来就会执行RejectedExecutionHandler拒绝策略,线程池提供了四种拒绝策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy

策略名称 描述
AbortPolicy 抛弃任务并抛出异常,这是线程池默认的拒绝策略
CallerRunsPolicy 由调用者执行任务
DiscardPolicy 丢弃任务
DiscardOldestPolicy 丢弃最老的任务并尝试执行任务
customerPolicy 也可以自定义拒绝策略

基本流程

用一张图来表示线程池的基本流程:

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);
}
  • 判断传递的command任务是为null,如果为null,抛出NPE
  • 从ctl变量获取c,后续根据c能获取到线程池的线程工作线程数量和线程池状态
  • 如果线程池中的工作线程数量小于了corePoolSize,那么就将该任务添加到工作线程(注意这里调用的addWorker传递的参数)。如果添加工作线程成功直接返回,如果失败再次重新从ctl获取c
  • 走到这一步说明上面的if条件不成立(不成立的情况比较多),接着判断线程池的状态如果是RUNNING并且将任务添加到阻塞队列成功
    • 再次从ctl中获取recheck,如果线程池状态不是RUNNING,那么就从阻塞队列中移除,如果移除成功就执行拒绝策略
    • 如果根据recheck检查状态是RUNNING或者从任务队列汇总移除失败,那么就根据recheck判断工作线程数是否为0,如果为0,那么就调用addWorker方法(这里调用的addWorker传递的参数是null和false
  • 如果上述的if条件不成立(根据c判断这状态不是RUNNING或者添加到阻塞队列失败),那么就就将任务添加到工作线程中,如果失败,执行拒绝策略(这里调用的addWorker传递的参数是command和false

从execute的方法和上面的基本流程图可以得出线程池中的线程使用顺序:核心线程 --> 阻塞队列 --> 最大线程。所以如果使用了无界阻塞队列,那么最大线程的设置就是无效的。

addWorker

从上面的方法可以看到整个execute方法中最终的依赖方法就是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();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

addWorker方法比较长,可以分为两部分:第一部分是判断线程池的状态、数量和新增工作线程数;第二部分就是新增工作线程。

  • 开启一个死循环,判断线程池状态,如果线程池状态不是RUNNING并且(状态是SHUTDOWN && 传递的任务为null && 阻塞队列不为空)这三个条件有一个不成立则返回false
  • 上述条件成立,则再次开启一个死循环
    • 获取线程池中工作线程数wc,如果wc大于了CAPACITY或者wc大于等于了corePoolSize或者maximumPoolSize(至于要大于哪个取决于addWorker方法第二个参数,如果传的true则比较的是corePoolSize,否则比较的是maximumPoolSize)返回false
    • 如果判断工作线程数成功,使用CAS的方式添加工作线程,添加成功跳出外层for循环
    • 如果添加工作线程失败,则需要判断状态是否相等,如果不等从外层for循环重新开始,直至符合条件结束
  • 走到这一步说明可以新增一个工作线程
  • 设置工作线程开始标志workerStarted为false,工作线程添加成功标志workerAdded为false
  • 将传递进来的command创建Worker,创建Worker时会创建线程,这里的线程是通过线程工厂创建的
  • 从worker中获取thread线程t
  • 如果t不为null
    • 获取ReentrantLock锁并进行加锁,因为这里需要对工作线程进行修改
    • 再次判断线程池状态如果不符合直接抛出异常进入finally代码块
    • 走到这里说明一切正常,将新创建的worker添加到workers集合中
    • 判断workers集合的长度size,如果size大于了largestPoolSize,则将size赋值给largestPoolSize
    • 将workerAdded置为true,说明添加worker成功
    • 释放锁
    • 判断workerAdded是否为true,如果为true,执行worker中的任务,并将workerStarted置为true
  • 进入finally代码块,判断workerStarted工作线程如果没有启动成功,则调用addWorkerFailed方法
  • 最后将workerStarted返回

以上是将任务添加到工作线程,并创建线程等操作,如果添加成功会执行任务,该任务的执行是在Worker类中run方法,执行runWorker方法。如果workerStarted最终还是false,则调用addWorkerFailed方法处理添加工作线程失败的逻辑

addWorkerFailed

如果添加工作线程失败,需要将该任务从worker中移除并将工作线程数减一,调用tryTerminate方法(调用这个方法的原因:这时候线程池可能不是RUNNING状态了,需要调用该方法将线程池关闭)

private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            workers.remove(w);
        decrementWorkerCount();
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}
  • 获取ReentrantLock锁,因为这里需要对工作线程进行写操作
  • 如果worker不为空,将该任务从workers中移除
  • 调用decrementWorkerCount方法对工作线程数减一
  • 调用tryTerminate方法,对线程池的状态进行判断做进一步操作,如果线程池状态不是RUNNING状态的话,将将线程池彻底关闭

runWorker

如果添加worker成功,那么就会执行worker中的任务

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    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;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}
  • 从传入的worker中获取任务Runnable以及获取当前线程wt
  • 解锁(这里释放锁的含义???)并设置完成标志completedAbruptly为true
  • 如果任务不为空或者任务task为null但调用getTask方法获取的任务不为null则进入while循环
  • 判断线程池的状态、线程的中断是否符合条件,如果不符合条件直接将线程中断,不继续执行
  • 调用beforeExecute方法执行业务之前的钩子函数
  • 执行任务逻辑
  • 执行内层finally代码块中的afterExecute方法,即业务逻辑之后的钩子函数
  • 进入中间的finally代码块,任务置为null(这个是为了下一次进入while循环的时候调用getTask方法获取任务),将completedTasks自增,解锁
  • 进入最外层finally代码块,调用processWorkerExit,处理worker退出逻辑

getTask

在执行worker中的任务时,如果task为null,就需要调用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.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        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;
        }
    }
}
  • 开启一个死循环,获取线程池的状态
  • 如果线程池的状态大于等于SHUTDOWN && (线程池状态大于等于STOP或者阻塞队列为空),那么将线程池中的worker数量减一并返回null
  • 获取工作线程数wc并判断超时timed(对工作线程是否应用超时等待的标志)
  • 校验线程数量以及是否超时: (如果wc大于最大线程数,表示在线程池运行时动态的将maximumPoolSize调小了,或者上一次的poll()操作已经超时,并且可以对工作线程应用超时等待),并且(如果wc大于1,或者任务队列为空)。
    • 如果上述条件成立,使用CAS方式将工作线程数减一,如果成功减一则返回null,如果不成功进入下一次循环
  • 走到这里说明可以从队列中获取任务,根据上面的timed进行判断是否需要超时等待从队列中获取任务
    • 如果从队列中获取的任务不为null则返回该任务;否则将timedOut置为true进入下一次循环。

上面情况下getTask为null

具有超过maximumPoolSize的线程数量,可能在运行时动态的设置了maximumPoolSize,将maximumPoolSize调小了,此时需要丢弃部分工作线程;
线程池处于STOP及其之后的状态,无论还有没有任务,无条件清除全部工作线程;
线程池处于SHUTDOWN状态,且 workQueue 为空,队列中的任务已执行完毕,清除工作线程;
如果线程数大于corePoolSize,则对超过的线程在keepAliveTime超时之后还没获取到任务就会返回null,如果设置了allowCoreThreadTimeOut为true(设置成功的要求是超时时间大于0),那么对全部线程应用超时时间,这里返回null用于清除多余的工作线程,控制线程数量。

processWorkerExit

在runWorker方法中,如果执行任务是否成功都会调用processWorkerExit(w, completedAbruptly)方法,只不过completedAbruptly参数不同,如果任务执行成功completedAbruptly为false,否则为true

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);
    }
}
  • 如果传入的completedAbruptly为true,说明任务执行失败,调用decrementWorkerCount方法将工作线程数减一
  • 加锁将已经执行完的任务从workers中移除,并释放锁
  • 尝试关闭线程池,这里主要是检查线程池的状态是否有变更
  • 判断线程池状态是否小于STOP,如果小于说明线程池的状态处于RUNNING或者SHUTDOWN状态,这时候可能就需要补充工作线程
    • 如果completedAbruptly为false,计算最小的线程数min,如果min等于0并且阻塞队列不为空,则将min置为1
    • 如果工作线程数大于了min,说明不需要补充线程,直接返回即可
    • 否则需要补充一个工作线程,调用addWorker(null, false);方法补充工作线程
posted @ 2021-07-21 13:44  扭不动的奥利奥  阅读(100)  评论(0)    收藏  举报