ThreadPoolExecutor

https://blog.csdn.net/djzhao/article/details/82192918

ThreadPoolExecutor是线程池类。对于线程池,可以通俗的将它理解为"存放一定数量线程的一个线程集合。线程池允许若个线程同时运行,允许同时运行的线程数量就是线程池的容量;当添加的到线程池中的线程超过它的容量时,会有一部分线程阻塞等待。线程池会通过相应的调度策略和拒绝策略,对添加到线程池中的线程进行管理。"

队列分类:

1. 直接传递。SynchronousQueue队列的默认方式,一个存储元素的阻塞队列而是直接投递到线程中。每一个入队操作必须等到另一个线程调用移除操作,否则入队将一直阻塞。当处理一些可能有内部依赖的任务时,这种策略避免了加锁操作。直接传递一般不能限制maximumPoolSizes以避免拒绝 接收新的任务。如果新增任务的速度大于任务处理的速度就会造成增加无限多的线程的可能性。

2. 无界队列。如LinkedBlockingQueue,当核心线程正在工作时,使用不用预先定义大小的无界队列将使新到来的任务处理等到中,所以如果线程数是小于corePoolSize时,将不会创建有入队操作。这种策略将很适合那些相互独立的任务,如Web服务器。如果新增任务的速度大于任务处理的速度就会造成无界队列一直增长的可能性。

3. 有界队列。如ArrayBlockingQueue,当定义了maximumPoolSizes时使用有界队列可以预防资源的耗尽,但是增加了调整和控制队列的难度,队列的大小和线程池的大小是相互影响的,使用很大的队列和较小的线程池会减少CPU消耗、操作系统资源以及线程上下文开销,但却人为的降低了吞吐量。如果任务是频繁阻塞型的(I/O),系统是可以把时间片分给多个线程的。而采用较小的队列和较大的线程池,虽会造成CPU繁忙,但却会遇到调度开销,这也会降低吞吐量。

BlockingQueue定义的常用方法如下:

 抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e, time, unit)
移除remove()poll()take()poll(time, unit)
检查element()peek()不可用不可用

        1)add(anObject):把anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则抛出异常

        2)offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.

        3)put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.

        4)poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null

        5)take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止

拒绝策略:

当Executor调用shutdown方法后或者达到工作队列的最大容量时,线程池则已经饱和了,此时则不会接收新的task。execute方法会调用RejectedExecutionHandler#rejectedExecution方法来执行饱和策略,在线程池内部预定义了几种处理策略:

1. 终止执行(AbortPolicy)。默认策略, Executor会抛出一个RejectedExecutionException运行异常到调用者线程来完成终止。

2. 调用者线程来运行任务(CallerRunsPolicy)。这种策略会由调用execute方法的线程自身来执行任务,它提供了一个简单的反馈机制并能降低新任务的提交频率。

3. 丢弃策略(DiscardPolicy)。不处理,直接丢弃提交的任务。

4. 丢弃队列里最老的一个任务(DiscardOldestPolicy)。如果Executor还未shutdown的话,则丢弃工作队列的最老的一个任务,然后执行当前任务。

// 线程集合。一个Worker对应一个线程。
private final HashSet<Worker> workers = new HashSet<Worker>();
// 阻塞队列。
private final BlockingQueue<Runnable> workQueue;
// 互斥锁
private final ReentrantLock mainLock = new ReentrantLock();
// “终止条件”,与“mainLock”绑定。
private final Condition termination = mainLock.newCondition();
// 线程池中线程数量曾经达到过的最大值。
private int largestPoolSize;
// 已完成任务数量
private long completedTaskCount;
// ThreadFactory对象,用于创建线程。
private volatile ThreadFactory threadFactory;
// 拒绝策略的处理句柄。
private volatile RejectedExecutionHandler handler;
// 保持线程存活时间。
private volatile long keepAliveTime;

private volatile boolean allowCoreThreadTimeOut;
// 核心池大小
private volatile int corePoolSize;
// 最大池大小
private volatile int maximumPoolSize;

1. workers
    workers是HashSet<Work>类型,即它是一个Worker集合。而一个Worker对应一个线程,也就是说线程池通过workers包含了"一个线程集合"。当Worker对应的线程池启动时,它会执行线程池中的任务;当执行完一个任务后,它会从线程池的阻塞队列中取出一个阻塞的任务来继续运行。
    wokers的作用是,线程池通过它实现了"允许多个线程同时运行"。

2. workQueue
    workQueue是BlockingQueue类型,即它是一个阻塞队列。用于存储任务并把任务投递给工作者线程的阻塞队列。当线程池中的线程数超过它的容量的时候,线程会进入阻塞队列进行阻塞等待。
    通过workQueue,线程池实现了阻塞功能。

3. mainLock
    mainLock是互斥锁,通过mainLock实现了对线程池的互斥访问。

4. corePoolSize和maximumPoolSize
    corePoolSize是"核心池大小",maximumPoolSize是"最大池大小"。它们的作用是调整"线程池中实际运行的线程的数量"。
    当新任务提交给线程池时(通过execute方法):

  • 向线程池中添加任务,当任务数量少于corePoolSize时,会自动创建thead来处理这些任务

  • 当添加任务数大于corePoolSize且少于maximmPoolSize时,不再创建线程,而是将这些任务放到阻塞队列中,等待被执行;当阻塞队列满了之后,继续创建thread,从而加速处理阻塞队列

  • 当添加任务大于maximmPoolSize时,根据饱和策略决定是否容许继续向线程池中添加任务,默认的饱和策略是AbortPolicy(直接丢弃)
              如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心池大小和最大池大小的值是在创建线程池设置的;但是,也可以使用 setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。

5. allowCoreThreadTimeOut和keepAliveTime
    allowCoreThreadTimeOut表示是否允许"线程在空闲状态时,仍然能够存活";keepAliveTime表示当前池里超出corePoolSize的线程如果是空闲的,将在存活指定的keepAliveTime时间后终止。这种机制主要是为减少资源的消耗,如果后期有新的活动任务,则又构造新的线程。该参数也可以通过setKeepAliveTime方法动态的修改,如果该参数设置为 Long.MAX_VALUE,则空闲的线程将一直存活。

6. threadFactory
    threadFactory是ThreadFactory对象。它是一个线程工厂类,"线程池通过ThreadFactory创建线程"。

7. handler
    handler是RejectedExecutionHandler类型。它是"线程池拒绝策略"的句柄,也就是说"当某任务添加到线程池中,而线程池拒绝该任务时,线程池会通过handler进行相应的处理"。

默认情况下,核心线程是在开始接收新任务时才初始创建,但是可以使用prestartCoreThread或prestartAllCoreThreads方法来动态的预开启所有的线程数。

源码解析:

几个状态相关的方法:
//COUNT_BITS=29
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
//c & (高3位为1,低29位为0的~CAPACITY),用于获取高3位保存的线程池状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
//c & (高3位为0,低29位为1的CAPACITY),用于获取低29位的线程数量
private static int workerCountOf(int c)  { return c & CAPACITY; }
//参数rs表示runState,参数wc表示workerCount,即根据runState和workerCount打包合并成ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }

也就是说32位含义:(高三位表示状态)+ (低29位表示线程数量)。

几种线程池状态:runState值
//能接受新提交的任务,并且也能处理阻塞队列中的任务;
private static final int RUNNING    = -1 << COUNT_BITS;
//关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);
private static final int SHUTDOWN   =  0 << COUNT_BITS;
//不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
private static final int STOP       =  1 << COUNT_BITS;
//如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
private static final int TIDYING    =  2 << COUNT_BITS;
/*在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。
进入TERMINATED的条件如下:
线程池不是RUNNING状态;
线程池状态不是TIDYING状态或TERMINATED状态;
如果线程池状态是SHUTDOWN并且workerQueue为空;
workerCount为0;
设置TIDYING状态成功。*/
private static final int TERMINATED =  3 << COUNT_BITS;

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//-1的二进制表示为全1,COUNT_BITS=29,所以RUNNING的前3位全1,后29位全0   
private static final int RUNNING    = -1 << COUNT_BITS;

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
         /*
         * Proceed in 3 steps:
         *
         * 1. 如果运行的线程少于corePoolSize,
         * 尝试开启一个新线程去运行command,command作为这个线程的第一个任务,并运行
         *
         * 2. 如果任务成功放入队列,我们仍需要一个双重校验去确认是否应该新建一个线程
         *(因为可能存在有些线程在我们上次检查后死了),或者进入这个方法后,pool被关闭了
         * 所以我们需要再次检查state,如果线程池停止了需要回滚入队列,
         * 如果池中没有线程了,新开启 一个线程
         *
         * 3. 如果无法将任务入队列(可能队列满了),需要新开区一个线程
         * 如果失败了,说明线程池shutdown或者饱和了,所以我们拒绝任务
         */
       
        int c = ctl.get();
        // 1.当运行的线程少于corePoolSize,
        // 则直接执行command任务,addworker(command,true)会产生一个新线程来执行这个任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
         // 2.线程池处于RUNNING状态,并且达到了corePoolSize线程数,将任务放入workQueue队列,但不执行addWorker(表明不创建新的线程)
        // 双重校验再次判断线程池的运行状态,如果不是运行状态,由于之前已经把command添加到workQueue中了,这时需要移除该command
        // 执行过后通过handler使用拒绝策略对该任务进行处理,整个方法返回
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //如果工作线程数为0,但阻塞队列又不空,必须要有一个线程来处理阻塞队列里的任务,所以添加一个线程,处理阻塞队列里的任务
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
         /*
         * 如果执行到这里,有两种情况:
         * 1. 线程池已经不是RUNNING状态(isRunning(c)==false);
         * 2. 线程池是RUNNING状态,但workerCount >= corePoolSize并且workQueue已满(workQueeue.offer(commond)==false)。
         * 这时,再次调用addWorker方法,但第二个参数传入为false,将线程池的有限线程数量的上限设置为maximumPoolSize;
         * 如果失败则拒绝该任务
         */
        else if (!addWorker(command, false))
            reject(command);
    }
/*1. 第一个参数为null,表示在线程池中创建一个线程,但不去启动;
  2. 第二个参数为true,将线程池的线程数量的上限设置为corePoolSize;为false,将线程池的有限线程数量的上限设置为maximumPoolSize*/
private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        /*双层循环,首先会检查线程池是否处于运行状态,核心线程池中是否还有空闲线程,都满足条件过后则会调用compareAndIncrementWorkerCount先将正在运行的线程数+1,数量自增成功则跳出循环,自增失败则继续从头继续循环*/
        for (;;) {
            int c = ctl.get();
            // 获取线程池运行状态,
            // 线程池的运行状态:runnbale=-1,shutdown=0,stop=1,tidying=2,terminated=3,存储在高3位
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            //如果线程池状态已经是非运行状态,表示不再接受新任务,所以不加入工作队列
            //下面三个条件,只要有一个不满足,就返回false,即也不加入工作队列
            //1.rs == SHUTDOWN,这表示关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中的任务
            //2.firstTask == null,走到这个判断的前提是rs = SHUTDOWN,已经不接受新任务,所以firstTask不为空就返回false
            //3.阻塞队列不为空,走到这个判断的前提是rs = SHUTDOWN,firstTask = null,如果阻塞队列也为空,说明队列中已经没有任务,不需要再添加线程
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                //如果当前线程数大于等于CAPACITY 或者 coreSize,不进入工作队列
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //根据我们最近获取的值进行cas更新+1,成功说明我们获取到的就是最新的值,跳出循环,失败说明此值已经被别人修改过了,可能修改了状态也可能是线程数量
                //比如当前线程池中已经有corePoolSize-1个线程,但现在同时有多个线程都执行到这里,线程1 CAS成功便跳出循环,线程2失败,继续获取最新的ctl值,执行内层循环,这时满足wc >= corePoolSize返回
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                //说明状态发生了改变,用retry重新判断一次shutdown返回
                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 {
            //将线程封装成工作线程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());
                    // rs < SHUTDOWN表示是RUNNING状态;
                    // 如果rs是RUNNING状态或者rs是SHUTDOWN状态并且firstTask为null,向线程池中添加线程。
                    // 因为在SHUTDOWN时不会在添加新的任务,但还是会执行workQueue中的任务(在runWorker方法中,因为firstTask==null,所以通过getTask从队列中得到任务)
                    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) {
                    //在被构造为Worker工作线程,且被加入到工作线程集合中后,执行线程任务,注意这里的start实际上执行Worker中run方法,所以接下来分析Worker的run方法
                   t.start();
             workerStarted = true;
      }
    }
  } finally {
    if (!workerStarted) //未能成功创建执行工作线程
      addWorkerFailed(w); //在启动工作线程失败后,将工作线程从集合中移除
  }
  return workerStarted;
}

Worker类

线程池中的每一个线程被封装成一个Worker对象,ThreadPool维护的其实就是一组Worker对象

在调用构造方法时,需要把任务传入,这里通过getThreadFactory().newThread(this);来新建一个线程,newThread方法传入的参数是this,因为Worker本身继承了Runnable接口,也就是一个线程,所以一个Worker对象在启动的时候会调用Worker类中的run方法。

Worker继承了AQS,使用AQS来实现独占锁的功能。为什么不使用ReentrantLock来实现呢?可以看到tryAcquire方法,它是不允许重入的,而ReentrantLock是允许重入的:

  1. lock方法一旦获取了独占锁,表示当前线程正在执行任务中;如果正在执行任务,则不应该中断线程;
  2. 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断;
  3. 线程池在执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态;
  4. 之所以设置为不可重入,是因为我们不希望任务在调用像setCorePoolSize这样的线程池控制方法时重新获取锁。如果使用ReentrantLock,它是可重入的,这样如果在任务中调用了如setCorePoolSize这类线程池控制的方法,会中断正在运行的线程。

所以,Worker继承自AQS,用于判断线程是否空闲以及是否可以被中断。

此外,在构造方法中执行了setState(-1);,把state变量设置为-1,为什么这么做呢?是因为AQS中默认的state是0,如果刚创建了一个Worker对象,还没有执行任务时,这时就不应该被中断

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        //允许中断,在Work初始化的时候将status设置成了-1,unlock之后,status变成了0
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                //tryAcquire方法是根据state是否是0来判断的,所以,setState(-1);将state设置为-1是为了禁止在执行任务前对线程进行中断。
                //正因为如此,在runWorker方法中会先调用Worker对象的unlock方法将state设置为0.
                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语句期间可能也执行了shutdownNow方法,shutdownNow方法会把状态设置为STOP
                //STOP状态要中断线程池中的所有线程,而这里使用Thread.interrupted()来判断是否中断是为了确保在RUNNING或者SHUTDOWN状态时线程是非中断状态的,因为Thread.interrupted()方法会复位中断的状态。
                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);
        }
    }

总结一下runWorker方法的执行过程:

  1. while循环不断地通过getTask()方法获取任务;
  2. getTask()方法从阻塞队列中取任务;
  3. 如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态;
  4. 调用task.run()执行任务;
  5. 如果task为null则跳出循环,执行processWorkerExit()方法;
  6. runWorker方法执行完毕,也代表着Worker中的run方法执行完毕,销毁线程。

这里的beforeExecute方法和afterExecute方法在ThreadPoolExecutor类中是空的,留给子类来实现。

completedAbruptly变量来表示在执行任务过程中是否出现了异常,在processWorkerExit方法中会对该变量的值进行判断。

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.
            /*
             * 如果线程池状态rs >= SHUTDOWN,也就是非RUNNING状态,再进行以下判断:
             * 1. rs >= STOP,线程池是否正在stop;
             * 2. 阻塞队列是否为空。
             * 如果以上条件满足,则将workerCount减1并返回null。
             * 因为如果当前线程池状态的值是SHUTDOWN且阻塞队列为空,没有任务需要处理了,返回null;如果是STOP及以上,不处理队列中的任务,也返回null。
             */
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            // timed变量用于判断是否需要进行超时控制。
            // allowCoreThreadTimeOut是允许线程池没有任务时销毁所有的进程;allowCoreThreadTimeOut默认是false,也就是核心线程不允许进行超时销毁;
            // wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;对于超过核心线程数量的这些线程,需要进行超时控制
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

             /* *目的是控制线程池的有效线程数量。由上文中的分析可以知道,在执行execute方法时,如果当前线程池的线程数量超过了corePoolSize且小于maximumPoolSize,并且workQueue已满时,则可以增加工作线程,但发生以下几种情况,说明需要销毁部分线程了。什么时候会销毁?当然是runWorker方法执行完之后,也就是Worker中的run方法执行完,由JVM自动回收。getTask方法返回null时,在runWorker方法中会跳出while循环,然后会执行processWorkerExit方法。
             *1.wc > maximumPoolSize的情况,说明对maximumPoolSize值做了调整,相应的需要减少线程数;
             *2.timed && timedOut 如果为true,说明允许线程池没有任务时销毁所有的进程或者wc > corePoolSize,并且workQueue已经为空了,说明可以销毁核心线程,或者说明了当前线程池中不需要那么多线程来执行任务了,可以把多于corePoolSize数量的线程销毁掉,保持线程数量在corePoolSize即可
             * 3.到了这里,说明要么线程数大于maximumPoolSize了,就可以销毁线程(wc>1肯定满足了);要么wc > corePoolSize且workQueue已经为空了,可以销毁线程;或者allowCoreThreadTimeOut =true且workQueue已经为空了,可以销毁线程;或者wc=1(表示线程池里唯一线程了),需要确保阻塞队列是空的,为空就可以销毁了。
             */
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    //poll:取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null
                    /*keepAliveTime的控制超时终止线程的作用体现在这里,上面通过wc > corePoolSize对timed赋值,
                    即对于大于corePoolSize的线程,在空闲时期(workQueue空了)通过poll方法取出null,在下一次循环的workQueue.isEmpty()判断后就会返回结束执行。
                    而corePoolSize的线程在空闲时期,通过take方法阻塞在这里,等待新的任务加入。*/
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    //take:取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
/*开始清理并且标记一个即将销毁的Worker。只有Worker所在的线程会被调用。除非,completedAbruptly被设置(为true)了,说明当前线程是意外停止了,假设Worker总数已经在退出之前重新调整了的。这个方法把当前Worker线程从WorkerSet中移除,并且如果由于用户Task异常导致退出,或者只有少于corePoolSize的Worker正在运行,或者队列并不是空的但是没有Worker的情况下,可能终止整个线程池,或者重新替换Worker。
1.判断是否是意外退出的,如果是意外退出的话,那么就需要把WorkerCount--
2.加完锁后,同步将completedTaskCount进行增加,表示总共完成的任务数,并且从WorkerSet中将对应的Worker移除
3.调用tryTemiate,进行判断当前的线程池是否处于SHUTDOWN状态,判断是否要终止线程
4.判断当前的线程池状态,如果当前线程池状态比STOP大的话,就不处理
5.判断是否是意外退出,如果不是意外退出的话,那么就会判断最少要保留的核心线程数,如果allowCoreThreadTimeOut被设置为true的话,那么说明核心线程在设置的KeepAliveTime之后,也会被销毁。
6.如果最少保留的Worker数为0的话,那么就会判断当前的任务队列是否为空,如果任务队列不为空的话而且线程池没有停止,那么说明至少还需要1个线程继续将任务完成。
判断当前的Worker是否大于min,也就是说当前的Worker总数大于最少需要的Worker数的话,那么就直接返回,因为剩下的Worker会继续从WorkQueue中获取任务执行。
7.如果当前运行的Worker数比当前所需要的Worker数少的话,那么就会调用addWorker,添加新的Worker,也就是新开启线程继续处理任务。*/
private void processWorkerExit(Worker w, boolean completedAbruptly) {
        // 如果completedAbruptly值为true,则说明线程执行时出现了异常,需要将workerCount减1;
        // 如果线程执行时没有出现异常,说明在getTask()方法中已经已经对workerCount进行了减1操作,这里就不必再减了。
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            // 从workers中移除,也就表示着从线程池中移除了一个工作线程
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
        
        // 根据线程池状态进行判断是否结束线程池
        tryTerminate();

        int c = ctl.get();
         /* 当线程池是RUNNING或SHUTDOWN状态时,如果worker是异常结束,那么会直接addWorker;
         * 如果allowCoreThreadTimeOut=true,并且等待队列有任务,至少保留一个worker;
         * 如果allowCoreThreadTimeOut=false,workerCount不少于corePoolSize。*/
        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);
        }
    }
final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
             /*
             * 当前线程池的状态为以下几种情况时,直接返回:
             * 1. RUNNING,因为还在运行中,不能停止;
             * 2. TIDYING或TERMINATED,因为线程池中已经没有正在运行的线程了;
             * 3. SHUTDOWN并且等待队列非空,这时要执行完workQueue中的task;
             */
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
             // 如果线程数量不为0,则中断一个空闲的工作线程,并返回
            if (workerCountOf(c) != 0) { // Eligible to terminate
                //作用是因为在getTask方法中执行workQueue.take()时,如果不执行中断会一直阻塞(在addWorker里通过t.start启动了线程,workQueue.take运行在t线程里,在这里调用t的中断方法).在下面介绍的shutdown方法中,会中断所有空闲的工作线程,如果在执行shutdown时工作线程没有空闲,然后又去调用了getTask方法,这时如果workQueue中没有任务了,调用workQueue.take()时就会一直阻塞。所以每次在工作线程结束时调用tryTerminate方法来尝试中断一个空闲工作线程,避免在队列为空时取任务一直阻塞的情况。
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // 这里尝试设置状态为TIDYING,如果设置成功,则调用terminated方法
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        // terminated方法默认什么都不做,留给子类实现
                        terminated();
                    } finally {
                        // 设置状态为TERMINATED
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }
private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        //在addWorker里通过t.start启动了线程,workQueue.take运行在t线程里,在这里调用t的中断方法,通过中断对workQueue.take解阻塞
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

在runWorker方法中,执行任务时对Worker对象w进行了lock操作,为什么要在执行任务的时候对每个工作线程都加锁呢?

下面仔细分析一下:

  • 在getTask方法中,如果这时线程池的状态是SHUTDOWN并且workQueue为空,那么就应该返回null来结束这个工作线程,而使线程池进入SHUTDOWN状态需要调用shutdown方法;
  • shutdown方法会调用interruptIdleWorkers来中断空闲的线程,interruptIdleWorkers持有mainLock,会遍历workers来逐个判断工作线程是否空闲。但getTask方法中没有mainLock;
  • 在getTask中,如果判断当前线程池状态是RUNNING,并且阻塞队列为空,那么会调用workQueue.take()进行阻塞;
  • 如果在判断当前线程池状态是RUNNING后,这时调用了shutdown方法把状态改为了SHUTDOWN,这时如果不进行中断,那么当前的工作线程在调用了workQueue.take()后会一直阻塞而不会被销毁,因为在SHUTDOWN状态下不允许再有新的任务添加到workQueue中,这样一来线程池永远都关闭不了了;
  • 由上可知,shutdown方法与getTask方法(从队列中获取任务时)存在竞态条件;
  • 解决这一问题就需要用到线程的中断,也就是为什么要用interruptIdleWorkers方法。在调用workQueue.take()时,如果发现当前线程在执行之前或者执行期间是中断状态,则会抛出InterruptedException,解除阻塞的状态;
  • 但是要中断工作线程,还要判断工作线程是否是空闲的,如果工作线程正在处理任务,就不应该发生中断;
  • 所以Worker继承自AQS,在工作线程处理任务时会进行lock,interruptIdleWorkers在进行中断时会使用tryLock来判断该工作线程是否正在处理任务,如果tryLock返回true,说明该工作线程当前未执行任务,这时才可以被中断。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值