深入理解ThreadPoolExecutor

一、概览

本文的全部分析基于JDK1.8,文章较长,请耐心阅读。

下面是本文所讲到的java线程池相关的API类图:

(一)Executor

Executor作为顶级接口,只有一个方法:

void execute(Runnable command);

execute()方法可能会在新的线程中执行,也有可能会在线程池中的某个线程中执行,也有可能直接在调用者线程中执行,这一切都依赖于Executor的具体实现。Executor其实可以理解成一个“任务管理器”,比起你直接创建Thread然后执行任务,它会替你完成线程的创建以及任务的执行,乃至线程的回收等等,当然这一切都依赖于Executor的具体实现。

(二)ExecutorService

ExecutorService接口继承了Executor,它是Executor接口重要的功能扩展,它抽象出了诸多方法,你大可把它当做真正的线程池接口,来看它的API截图:

(三)AbstractExecutorService

AbstractExecutorService是一个抽象类,它实现了ExecutorService接口,并且ExecutorService中定义的大多数方法都在该类中得到了实现,比如submit()的几个重载方法以及跟invoke相关的一些方法等等,来看它的API截图:

(四)ThreadPoolExecutor

ThreadPoolExecutor类是线程池中最核心的一个类,继承了AbstractExecutorService,它是线程池最具体的实现,因为它实现了Executor中所定义的execute()方法。对于该类的解析正是本文的重点。

(五)Executors

Executors是一个工具类,线程池的静态工厂,它已经为大多数使用场景预设了线程池,提供了快捷创建线程池的静态方法。在平时使用线程池的过程中,大多是用Executors来直接创建。

二、ThreadPoolExecutor-初步理解

(一)线程池的核心参数

ThreadPoolExecutor一共提供了四个public的构造方法,我们看它最重要的一个构造方法开始,因为其他三个最终都会调用到下面这个:

public class ThreadPoolExecutor extends AbstractExecutorService {
    ...
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

从构造方法着手的主要原因是为了去了解ThreadPoolExecutor里面的核心参数,下面一一讲解:

  • corePoolSize:线程池核心线程数

    核心线程池大小描述的是通常情况下线程池中活跃的线程最小数量,一般来讲,一个线程池被创建后,它的初始线程个数是0。当有任务来的时候,就会创建线程执行任务,当池中的线程个数大于corePoolSize,就会将任务放入工作队列(workQueue)

  • maximumPoolSize:线程池最大线程数

    阻塞队列也放不下任务的时候,就会继续创建线程来执行任务,但是最终该池中的线程个数不能超出maximumPoolSize。

  • keepAliveTime:线程保持活跃而不销毁的时间

    一般来讲,keepAliveTime只会在线程个数超过corePoolSize的时候发挥作用,它会将线程保留keepAliveTime的时间,然后销毁,直到池中线程个数等于corePoolSize。但如果调用了allowCoreThreadTimeOut()方法,就相当于允许核心线程池中的线程也超时销毁,那么不管什么线程最终都会因为超出keepAliveTime时间而被销毁,直至最终线程个数为0。

  • unit:TimeUnit类型,作为keepAliveTime的时间单位

    其实这是一个很有意思的工具类,也是自java 1.5加入到java.util.concurrent包内的,它封装了一些对当前线程的操作,比如线程sleep就推荐用TimeUnit去写,这里我们只需关注它的七个时间枚举:

TimeUnit.DAYS;              //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒
  • workQueue:工作队列

    当池中线程个数大于corePoolSize的时候,任务就存放于此等待执行。workQueue类型为BlockingQueue,jdk已经内置了几种实现,通常会取以下几种类型:

    1. ArrayBlockingQueue:有界队列,基于数组的先进先出队列,此队列创建时,需要指定大小
    2. LinkedBlockingQueue:无界队列,基于链表的先进先出队列,如果创建时没有指定队列大小,则默认为Integer.MAX_VALUE。它其实间接的造成maximumPoolSize失效,同时也存在内存溢出的隐患
    3. SynchronousQueue:任务不用排队而是直接提交的队列,该队列下任务过来会直接新建线程执行
    4. PriorityBlockingQueue:和ArrayBlockingQueue差不多,不同的是它可以实现自动扩容,其中的元素要实现Comparatable接口,在创建时要传入comparator,这些都是用于元素的排序
  • threadFactory:线程工厂

    线程工厂,顾名思义,就是用来生产线程的,它可以为线程设置一些属性,比如线程名、守护线程等,它还可以设置在主线程中对于子线程的未捕获异常的处理策略等等。一般来讲Executors中提供的默认线程工厂就足够了。

  • handler:表示拒绝执行任务的策略

    它是RejectedExecutionHandler类型的引用,它会在队列饱和或者线程池被关闭时被调用。RejectedExecutionHandler接口在ThreadPoolExecutor中有四种类型的内部实现类:

    1. ThreadPoolExecutor.AbortPolicy(默认):丢弃任务,并抛出RejectedExecutionException异常
    2. ThreadPoolExecutor.CallerRunsPolicy:用调用者的线程执行任务
    3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中存在最久未被执行的任务,然后重新尝试execute执行,但如果线程池被关闭,任务会直接被丢弃
    4. ThreadPoolExecutor.DiscardPolicy:丢弃任务,不会抛出异常

(二)线程池的执行流程

在上面的众多参数中,最需要理解的是corePoolSizemaximumPoolSize,可以做一个总结就是:

  1. 池中线程数小于corePoolSize,新任务过来会直接创建新线程来执行。
  2. 池中线程数大于等于corePoolSize,且workQueue未饱和,新任务过来会加入workQueue等待执行。
  3. 池中线程数大于等于corePoolSize,且workQueue已饱和,但线程数小于maximumPoolSize,新任务过来会创建新线程来执行。
  4. 池中线程数大于等于corePoolSize,且workQueue已饱和,并且线程数等于maximumPoolSize,新任务会被拒绝,拒绝策略就是handler。

所以,在我们接下来要探究线程池的细枝末节之前,能够大体了解它的执行流程是很有帮助的,正如上面总结的,线程池的执行大体就是在围绕corePoolSize和maximumPoolSize在进行,大家也可以参考下图来有个更直观的印象:

(三)线程池状态

先来看下面ThreadPoolExecutor中很重要的一段代码:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 线程数量占用的位数:32-3=29
private static final int COUNT_BITS = Integer.SIZE - 3;
// 最大线程数,1*2的29次方再减1,足够大了
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
// 运行状态存储在高位(前三位),后面都是29个0
// 111(有符号)
private static final int RUNNING    = -1 << COUNT_BITS;
// 000
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 001
private static final int STOP       =  1 << COUNT_BITS;
// 010
private static final int TIDYING    =  2 << COUNT_BITS;
// 011
private static final int TERMINATED =  3 << COUNT_BITS;
// Packing and unpacking ctl
// 处理ctl的值,从中获取高3位得到线程池状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 处理ctl的值,从中获取低29位得到线程数量
private static int workerCountOf(int c)  { return c & CAPACITY; }
// 将runState(线程池状态)和workerCount(线程数量)通过 [按位或运算符] 合并成ctl的值
private static int ctlOf(int rs, int wc) { return rs | wc; }
/*
 * Bit field accessors that don't require unpacking ctl.
 * These depend on the bit layout and on workerCount being never negative.
 */
// 下面的三个方法从名字上也能猜出它在干嘛
private static boolean runStateLessThan(int c, int s) {
    return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}

ctl是ThreadPoolExecutor的一个重要属性,它身兼两职,同时记录着线程池的线程数量线程池状态。怎么做到的呢?从上面的代码可以看到,Integer.SIZE是32,COUNT_BITS是29,线程池状态均左移位29,所以通过ctl.get()获取的值,32位中,前三位用于表示线程池状态,后29位用于表示线程的数量。这样一来,一个变量就可以代表两种含义,这在很大程度上减少获取变量时锁的竞争。同时,AtomicInteger这个类是线程安全的,它可以通过CAS操作达到无锁并发(如果不清楚CAS是什么的,可以自行查阅资料)。线程池的状态一共有五种:

  • RUNNING:运行状态,也是线程池的初始状态,会接收新任务并处理队列中的任务
  • SHUTDOWN:停工状态,不再接收新任务,但正在执行的任务会继续执行,队列中的任务也会得到处理
  • STOP:停止状态,不再接收新任务,正在执行的任务会被中断并且队列中的任务不会被处理
  • TIDYING:清空状态,所有任务都停止,队列被清空,工作线程也全部销毁(workerCount=0),并且会接着调用terminated()方法
  • TERMINATED:终止状态,terminated()方法调用后到此状态,此时线程池被销毁

三、ThreadPoolExecutor-任务提交

(一)execute()方法

接着想要深入理解ThreadPoolExecutor,就要来到一切的源头execute()方法。

向线程池提交任务有这2种方式,execute()方法和submit()方法。上面提到过submit()方法是ExecutorService接口中定义的,并且在AbstractExecutorService这个抽象类中就已得到实现。submit()方法主要是将任务封装成FutureTask的形式来提交,达到对任务进行监控的目的,比如中断任务的执行,获取执行结果等等,这一切的监控行为都是FutureTask自己维护的。submit()方法最终都会调用execute()方法,execute()方法是在ThreadPoolExecutor中实现的,来看下源码:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    /*
     * 第一阶段:
     * 如果线程数量 < 核心线程数量,就通过addWorker()新起线程执行任务,该线程占用corePoolSize个数
     */
    if (workerCountOf(c) < corePoolSize) {
        /*
         * 如果addWorker()成功,则execute()返回
         */
        if (addWorker(command, true))
            return;
        /*
         * 如果addWorker()失败,则刷新ctl(ctl中保存了两个信息,线程池状态和线程个数,后面不再赘述)
         * 失败原因可能是:
         *     1.线程池状态是SHUTDOWN,此时不接收新任务
         *     2.并发的原因,导致workerCountOf(c) >= corePoolSize
         */
        c = ctl.get();
    }
    /*
     * 第二阶段:
     * 如果线程池是RUNNING状态,并且任务添加队列成功
     */
    if (isRunning(c) && workQueue.offer(command)) {
        /*
         * 刷新ctl
         */
        int recheck = ctl.get();
        /*
         * 双重校验线程池是否还是RUNNING状态,如果不是,则从队列中移除任务,并执行拒绝策略
         */
        if (! isRunning(recheck) && remove(command))
            reject(command);
        /*
         * 如果在上面双重校验线程池状态后,线程池依然是RUNNING状态,或者remove()失败,则检测线程数量是否为0
         * 		如果为0,则新起线程,此时线程不会有初始任务,并且占用maximumPoolSize个数
         * 		为什么要这样做呢?因为此时只需保证至少存在一个线程可以执行后续任务即可
         * remove()失败的可能原因:刚好有线程执行完毕并且消耗了队列中的此任务
         */
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    /*
     * 第三阶段:
     * 如果线程池不是RUNNING状态,或者队列饱和,则尝试新起线程执行任务,并占用maximumPoolSize个数
     * 如果此时新起线程仍然失败,则执行拒绝策略
     * 新起线程仍然失败的可能原因:
     *     1.线程池已经shutdown了
     *     2.线程数量 > maximumPoolSize 
     */
    else if (!addWorker(command, false))
        reject(command);
}

(二)执行流程图:

(三)小结:

execute()方法基本都是在对【线程数量】和【线程池状态】这两个要素进行管控,然后根据不同的管控结果进行addWorker()或者reject()操作,真正的添加线程并启动任务是在addWorker()中做的。所以,addWorker()方法是我们接下来要重点研究的。

四、ThreadPoolExecutor-添加线程

(一)addWorker()方法

来看源码:

private boolean addWorker(Runnable firstTask, boolean core) {
    /*
     * 外层循环,主要职责是从线程池状态上把关
     * retry是一个标记,相当于goto的思想
     */
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // Check if queue empty only if necessary.
        /*
         * 判断线程池状态的代码,这段代码反过来理解会轻松很多
         * 首先,上面讲到的线程池状态一共四个:RUNNING=-1,SHUTDOWN=0,STOP=1,TIDYING=2,TERMINATED=3
         * 		数值越大,就越接近死亡状态
         * 解析出来就是 A && B 都满足的时候,返回false,可以理解从【不可添加线程】状态。
         * 反过来就是 !A || !B,只要是其中任意一种满足,就是【可添加线程】状态
         * !A:只有一个状态,那就是RUNNING,只要是这个状态,就【可添加线程】
         * !B:rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty(),什么场景呢?就是线程池
         *		状态虽然为SHUTDOWN,但是没有初始任务,单纯为了起一个线程,为的就是执行队列中残存的任务。
         * 		只要满足这几个条件,也【可添加线程】
         * 其他任何情况,都是直接返回false,【不可添加线程】
         */
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
        /*
         * 内层循环,主要职责是从线程数量把关,并且【线程数量+1】
         */
        for (;;) {
            int wc = workerCountOf(c);
            /*
             * A || B,满足其中任意一种,就返回false,就是【不可添加线程】状态。
             * A:wc >= CAPACITY,线程数量 > 线程数量统计容量上限,都爆表了自然【不可添加线程】
             * B:wc >= (core ? corePoolSize : maximumPoolSize),线程数量的【临界值】,
             * 		是corePoolSize或者maximumPoolSize,取决于core这个参数,上面有讲到过。如果超出
             * 		这个【临界值】,此次添加线程也是返回false,就是【不可添加线程】
             * 总结来说就是,不爆表,也不超出临界值,就【可添加线程】
             */
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            /*
             * CAS操作【线程数量+1】,成功则跳出外层循环
             */
            if (compareAndIncrementWorkerCount(c))
                break retry;
            /*
             * 失败则刷新ctl
             */
            c = ctl.get();  // Re-read ctl
            /*
             * 然后判断线程池状态是否发生了变化
             * 如果发生了变化,则继续外层循环
             * 如果没有发生变化,则继续内层循环,执行CAS操作【线程数量+1】(CAS操作失败,
             * 		是因为workerCount发生改变,所以要继续判断内层循环)
             */
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    // 线程成功启动标志
    boolean workerStarted = false;
    // 添加worker线程成功标志
    boolean workerAdded = false;
    Worker w = null;
    try {
        /*
         * Worker本身就是Runnable,目前大体只要知道会用ThreadFactory创建一个线程,
         * 		然后赋予worker的thread成员变量
         */
        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());
                /*
                 * 对应线程池状态的双重检查,需要满足一下两点:
                 * 1.rs = RUNNING
                 * 2.rs == SHUTDOWN && 没有需要新接收的任务
                 */
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    /*
                     * 检查线程是否已经被start过,确保每个线程只能start一次
                     */
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    /*
                     * 将worker添加进workers
                     * workers本身是HashSet,非线程安全,所以要上锁操作
                     */
                    workers.add(w);
                    int s = workers.size();
                    /*
                     * 刷新历史最大线程个数
                     */
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    /*
                     * 更新标志
                     */
                    workerAdded = true;
                }
            } finally {
                /*
                 * 线程池解锁
                 */
                mainLock.unlock();
            }
            if (workerAdded) {
                /*
                 * 在worker成功添加进workers后,线程启动并更新标志
                 */
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        /*
         * 上面的很多因素都会导致addWorker失败,此时需要确保执行失败后的处理措施
         */
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

(二)参数解析:

  1. 第一个参数firstTask:表示新起线程并需要执行的初始任务,可以为空
  2. 第二个参数core:如果为true则表示占用corePoolSize个数,也就是线程数量会和corePoolSize进行比较,如果为false则表示占用maximumPoolSize个数,也就是线程数量会和maximumPoolSize进行比较。

(三)四种传参方式的使用场景:

在execute()方法中用到了前3种,最后一种其实也很重要,它可以帮助预加载全部的核心线程池,来看一下:

  1. addWorker(command, true):corePoolSize未满,此时就是在接收任务并新起线程执行它,所以在方法里面,线程数量会跟corePoolSize这个临界值比较
  2. addWorker(command, false):队列已饱和,此时也是在接收任务并新起线程执行它,但是不同的它尝试将线程扩容至maximumPoolSize,所以线程数量会跟corePoolSize这个临界值比较
  3. addWorker(null, false):此时是在做一种保护措施,新起一个线程但并不会给它分配初始任务,只是确保至少有一个线程消费队列中的任务。它的场景很可能是调用了shutdown(),这时候不会接受新任务,但队列中的任务依然会得到执行
  4. addWorker(null, true):它其实是在预启动核心线程池,这个方法新起的线程都没有初始任务,并且都占用corePoolSize个数,这些线程的职责就是等待从队列中获取任务并执行。实际使用中就是调用prestartAllCoreThreads()方法,这样就可以预先启动corePoolSize个线程,随时待命。

(四)执行流程图:

(五)小结:

addWorker()总体来说就是在添加并启动线程,只不过它会围绕【线程数量】和【线程池状态】做很多校验,以确保是【可添加线程】状态才会真正的添加线程。在设计上值得注意的是,对HashSet这个非线程安全类的操作是使用了重入锁的,并且只对最小最必要的代码块上锁,很大程度上减轻了并发时对锁的竞争。

接下来我们看看对线程的封装——Worker,Worker的设计也有很多的巧妙之处。

五、ThreadPoolExecutor-内部类Worker

/*
 * Worker实现了Runnable,所以其本身就是任务,并且Worker继承了AQS,所以其本身就是锁
 */
private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;
    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
    /** Per-thread task counter */
    volatile long completedTasks;
    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        // state初始赋值为-1,控制线程在启动后才能中断
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        // 将worker自己作为参数通过ThreadFactory创建线程
        this.thread = getThreadFactory().newThread(this);
    }
    /** Delegates main run loop to outer runWorker  */
    /*
     * 真正的执行任务了
     */
    public void run() {
        runWorker(this);
    }
    // Lock methods
    //
    // The value 0 represents the unlocked state.
    // The value 1 represents the locked state.
    /*
     * 重写AQS的isHeldExclusively()
     * 0代表【未锁定】状态
     * 1代表【锁定】状态
     */
    protected boolean isHeldExclusively() {
        return getState() != 0;
    }
    /*
     * 尝试获取锁
     * 重写AQS的tryAcquire()
     */
    protected boolean tryAcquire(int unused) {
        /*
         * 可以看到,这边每次都是用CAS操作,将状态从0->1,而不是递增,其实这已代表这是个【非重入锁】
         */
        if (compareAndSetState(0, 1)) {
            /*
             * 设置当前线程为锁的owner,其实就是当前线程获取到锁
             */
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
    /*
     * 尝试释放锁
     * 重写AQS的tryRelease()
     */
    protected boolean tryRelease(int unused) {
        /*
         * 释放锁,并且将锁状态设置为0(对比一下,锁在最开始的初始化状态为-1)
         */
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }
    /*
     * 以下四个public方法都是对AQS中方法的封装
     */
    public void lock()        { acquire(1); }
    public boolean tryLock()  { return tryAcquire(1); }
    public void unlock()      { release(1); }
    public boolean isLocked() { return isHeldExclusively(); }
    /*
     * 顾名思义,只有启动后的线程才能被中断
     * shutdownNow()方法最终会调用到该方法,因为shutdownNow不论你线程是否在运行,都会立即中断线程,
     *  	所以该方法中的中断不需要获取worker锁
     */
    void interruptIfStarted() {
        Thread t;
        /*
         * 可以看到state的状态只要求>0,所以在线程执行任务期间是可以中断的
         */
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

(一)Worker作为Runnable的作用

  1. Worker实现了Runnable接口,所以Worker本身就是任务。可以看Worker的构造函数,它将接收的任务赋值给firstTask成员变量,然后将自己作为参数传递给ThreadFactory线程工厂,这样创建出线程再赋值给thread变量。
  2. 继续看Worker的run()方法,很简单,就runWorker(this)一行代码,将自身作为参数传递。所以thread.start()启动线程后,执行的任务就是firstTask中的run(),这一点可以在接下来对runWorker()方法的讲解中得到验证。

(二)Worker作为AQS的作用

  1. Worker继承了AbstractQueuedSynchronizer(AQS——队列同步器),AQS是java中用来构建锁和其他并发组件的基础框架,它基于模板模式实现,其中没有任何abstract抽象方法,而是在需要子类实现的方法中都直接抛出UnsupportedOperationException异常。AQS有几个非常重要的成员变量,其中很重要的一个就是state,state代表的是锁的获取情况,对于state的操作都是CAS操作。(AQS还是值得学习一波的,有不清楚的可以自行查阅相关资料)
  2. 在Worker继承AQS时,它没有重写tryAcquireShared()和tryReleaseShared()方法,只是重写了tryAcquire()和tryRelease()方法,所以它是一个简单的独占锁。其实最重要的是它实现了非重入锁功能,大家可以看tryAcquire()方法中的注释。所以Worker通过继承AQS,就简化了线程在执行任务时对于锁的获取和释放,并且通过锁的获取状态控制线程中断。

(三)非重入锁设计必要性

tryAcquire()方法是获取worker锁的方法,它被tryLock()方法封装,所以谁调用了tryLock()方法:

/*
 * 该方法主要就是中断空闲线程,何为空闲线程,就是不在执行任务的线程,反应到worker锁上就是
 *  	没有获取到锁的线程,即state=0的线程
 */
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();
    }
}

interruptIdleWorkers(boolean onlyOne)是ThreadPoolExecutor中封装的方法,只在这一处调用到了tryLock(),在ThreadPoolExecutor中,一共有以下方法调用了interruptIdleWorkers():

  • setCorePoolSize()
  • setMaximumPoolSize()
  • setKeppAliveTime()
  • allowCoreThreadTimeOut()
  • shutdown()

此次对于非重入锁的必要性的分析,我们只以setCorePoolSize()为例,因为其他方法都与其类似。

public void setCorePoolSize(int corePoolSize) {
    if (corePoolSize < 0)
        throw new IllegalArgumentException();
    int delta = corePoolSize - this.corePoolSize;
    this.corePoolSize = corePoolSize;
    if (workerCountOf(ctl.get()) > corePoolSize)
        /*
         * 【核心线程个数】>【当前线程个数】时,会尝试中断空闲线程
         */
        interruptIdleWorkers();
    else if (delta > 0) {
        // We don't really know how many new threads are "needed".
        // As a heuristic, prestart enough new workers (up to new
        // core size) to handle the current number of tasks in
        // queue, but stop if queue becomes empty while doing so.
        int k = Math.min(delta, workQueue.size());
        while (k-- > 0 && addWorker(null, true)) {
            if (workQueue.isEmpty())
                break;
        }
    }
}

setCorePoolSize()方法主要是重新设置线程池的核心线程个数的,假如重新设置的【核心线程个数】>【当前线程个数】,那么就需要中断那些空闲线程。

那么何为空闲线程呢?线程在执行任务时,都会通过w.tryLock()获取worker锁,所以说获取到锁的线程就是工作中的线程,未获取到锁的线程就是空闲线程,这一点也可以在接下来对runWorker()方法的解析中得到验证。

反过来想,假如worker是可重入锁,那工作中的线程即使获取到worker锁也会被中断,因为该锁可重入,这样就达不到【只中断空闲线程】的目的。这也是shutdownNow()方法不在上面队列中的原因,shutdownNow()的调用会中断你所有线程,无论线程是否正在工作,所以shutdownNow()对线程的中断不需要获取worker锁,这一点在上面的interruptIfStarted()中也能得到验证。

这里强调一点,ThreadPoolExecutor有个成员变量mainLock,它是ReentrantLock重入锁,千万不要跟这里的worker锁搞混淆,它们的作用不一样。mainLock是线程池锁,在ThreadPoolExecutor的很多方法中都有用到,包括上面讲到的execute()和addWorker(),它主要是控制外部线程对线程池对象的安全调用。而worker锁主要是在对线程池内部线程做管理,控制它们的中断。

worker非重入锁的设计对于线程池中线程的中断控制可以做如下总结:

  • worker对象刚new出来,锁的状态是state=-1,所以无论是interruptIfStarted()还是interruptIdleWorkers(),都无法中断线程。
  • 对于setCorePoolSize(),setMaximumPoolSize(),setKeppAliveTime(),allowCoreThreadTimeOut()和shutdown(),它们最终会调用interruptIdleWorkers(),非重入锁的设计使得他们可以做到【只中断空闲线程】。
  • 对于shutdownNow(),它最终会调用interruptIfStarted(),该方法不需要获取worker锁,所以它可以做到【立即中断所有线程】

继续线程池整个流程的分析,Worker的run()方法中调用了runWorker(this),这里面做了什么呢?

(四)执行任务:runWorker()

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    /*
     * 该方法最终会调用worker的tryRelease(),它会将worker的state设置为0,这样就具备了被中断的初始条件
     */
    w.unlock(); // allow interrupts
    /*
     * 该值用于记录代码是否突然性完成,其实很好理解,它就是用来标志代码是否发生异常
     * 它的值会影响在最后处理worker的退出时,需不需要新起worker来替换此时的这个worker
     */
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            /*
             * 获取worker锁
             */
            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 {
                /*
                 * 任务置为空,worker完成任务数+1,worker锁解锁
                 */
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        /*
         * 只有从获取worker锁到释放worker锁整个期间,都没有出现异常,这个值才为false,因为只要有异常,
         *  	最后都会直接到下面的finally中
         * false:代表整个过程没有异常
         * true:代表过程中出现了异常
         */
        completedAbruptly = false;
    } finally {
        /*
         * 处理worker的退出
         */
        processWorkerExit(w, completedAbruptly);
    }
}

调用了runWorker()的线程才算真正启动,并且一个线程只启动这么一次。一个线程的诞生,或许为了执行新任务,或许为了执行队列中的任务,但无论如何,调用了runWorker()后,等待这个线程的要么是执行任务然后阻塞在getTask()上,如此往复,要么就是灭亡。

(五)小结:

整个runWorker()的代码读起来很顺畅,三个try()三个finally(),都有各自的作用,总结一下:

  1. worker锁先释放,这样state会被设置成0,此时就具备了线程被中断的初始条件了
  2. firstTask不为空,或者getTask()不为空,就会进入到执行task的run()方法的
  3. 执行任务之前,会先获取worker锁
  4. 开始执行task.run(),在前后会有自定义业务场景的执行,beforeExecute(wt, task)和afterExecute(task, thrown)
  5. 然后会释放worker锁
  6. 2-5的过程其实都在整个while循环中,所以就分7-8两种情况
  7. 假如任务执行过程中有任何异常,worker线程都会终止,processWorkerExit()会处理worker的退出流程,下面会有对processWorkerExit()方法的讲解
  8. 假如无任何异常,worker可能会在getTask()上阻塞,一直等取出任务,或者getTask()超时,超时就会返回null,这样就会结束while循环,也就意味着worker线程进入退出流程。下面就会进行getTask()的讲解。

六、ThreadPoolExecutor-任务获取

(一)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.
        /*
         * 对于线程池状态的判断,满足其一就直接返回null
         * 1.线程池状态 > SHUTDOWN会返回null
         * 2.或者线程池状态 = SHUTDOWN的情况下,队列为空,也会直接返回null
         */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);
        // Are workers subject to culling?
        /*
         * 线程过期是否生效,满足其一即可
         * allowCoreThreadTimeOut = true 或者 当前线程数量 > 核心线程池数量
         */
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        /*
         * 对于线程过期并超时的判断:
         * 意思是说,只要队列是空或者线程数不止一个,那么线程超时应该立马得到反馈
         * 反馈的结果就是线程数减一并返回null
         */
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        try {
            /*
             * 会根据是否允许线程生效,来调用队列的不同方法
             * 1.生效:调用poll(),poll会阻塞keepAliveTime时长,超时后还没有获取到任务,
             *  	就会返回null,之后timedOut设置为true,会在下次循环中因线程超时退出
             * 2.不生效:调用take(),take在队列中没有任务时会一直阻塞
             */
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            /*
             * 线程如果被中断,会重新开始整个循环
             */
            timedOut = false;
        }
    }
}

(二)执行流程图:

(三 )小结:

  1. 获取ctl

  2. 进行线程池状态的判断,判断规则:

    • 线程池状态为SHUTDOWN,并且队列为空,则【线程数-1】然后返回null
    • 或者线程池状态>SHUTDOWN,也同样可以【线程数-1】然后返回null

    这是很有必要的,SHUTDOWN状态是可以继续执行队列中的任务的,如果>SHUTDOWN了,那么方法肯定要尽快返回。

  3. 是否允许线程过期生效,判断规则:

    • 如果设置过了allowCoreThreadTimeOut=true,那么timed=true,即线程过期生效
    • 如果当前线程数 > 核心线程数,那么也生效
  4. 线程是否超时,如果满足,则CAS【线程数-1】并返回null,如若CAS操作是吧,则重新开始循环,判断规则:

    • 线程数 > maximumPoolSize或者线程过期超时
    • 同时还有满足,线程数 > 1或者队列为空
  5. 会根据timed值来调用队列不同的任务获取方法:

    • true:调用poll(),poll会阻塞keepAliveTime时长,超时后还没有获取到任务,就会返回null,之后timedOut设置为true,会在下次循环中因线程超时退出
    • false:调用take(),该方法会一直阻塞直到获取任务
  6. 获取任务的整个过程,如若线程被中断,会重置timedOut = false,然后重新整个循环

七、ThreadPoolExecutor-线程退出:

(一 )processWorkerExit()方法

在讲runWorker()方法的时候有说过,worker会因为执行异常或者getTask()超时而进入worker的退出流程,下面来看看这个退出流程做了什么,源码:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    /*
     * 如果是runWorker()中发生异常,这边就要【线程数-1】
     */
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        /*
         * 将该worker处理的任务总数计入到线程池处理的任务总数中去,
         *  	然后从集合中移除该worker(代表该worker的终结)
         */
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    /*
     * 这是一种【尝试终止线程池】的操作
     * 在整个ThreadPoolExecutor中,有好几个方法都有调用到它,其实它包含一种含义,
     *  	就是对线程池有趋于“死亡”操作的方法都会【尝试终止线程池】,比如shutdown()
     */
    tryTerminate();
    int c = ctl.get();
    /*
     * 下面都在讨论是否需要新起worker来代替该worker
     * 一切的前提是线程池的状态 < STOP,即RUNNING或者SHUTDOWN
     */
    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);
    }
}

(二)小结:

  1. 如果是runWorker()中发生异常,需要执行【线程数-1】操作
  2. 线程池上锁
  3. 将worker处理过的任务计入到线程池处理的任务总数中去,然后从集合中移除该worker(代表该worker的终结)
  4. tryTerminate()是一种【尝试终止线程池】的操作,在整个ThreadPoolExecutor中,有好几个方法都有调用到它,它的含义就是,任何对线程池有趋于“死亡”操作的方法都会【尝试终止线程池】,比如shutdown()
  5. 判断是否需要新起worker来代替该worker,判断逻辑大体为:
    • 首先必须满足线程池的状态 < STOP,即RUNNING或者SHUTDOWN
    • 如果是runWorker()中发生异常,就addWorker
    • 如果runWorker()中没有发生异常,但是判断出“目前的线程数量偏少”了,就addWorker。“目前的线程数量偏少“的判断是很有意思的,它的核心思想就是【维护核心线程池】或者【队列不空,至少维护存在一个线程】

八、总结:

本篇文章主要是对线程池原理的剖析,由于篇幅的原因,对于Executors静态工厂提供的几种创建线程池的方法并未提及。

然后,对于线程池中如何终止线程池这一话题,也只在“非重入锁设计必要性”中稍有讲到,这一部分内容其实很有必要单独拎出来进行讲解,里面有很多Doug Lea大神的思想精华。

posted @ 2021-03-03 21:50  M芒果先生  阅读(1397)  评论(0)    收藏  举报