ThreadPoolExecutor - java并发系列(一)

227 阅读22分钟

ThreadPoolExecutor

一、概述

线程的创建和销毁是比较耗时的,池的概念都是为了防止对象频繁的创建和销毁,类似的概念还有连接池,或者更通用的叫对象池。但是跟我们对象池的模型不太一样,对象池常用的方法是borrowObject和returnObject。用的时候借出来,不用的时候还回去。数据库连接池也是一样的,对getConnection获取线程,close(并没有真正的close)的时候还回去。

而jdk的ThreadPoolExecutor(线程池),是一个生产者消费者的模型。线程池是一个消费者,而我们不断生成任务,不断的submmit/execute任务。虽然是生产者消费者模型,但是,大体来说,也是一个池的概念。比如有最大/最小线程数,线程的最大空闲时间,线程工厂等等。也有些,生产/消费者模型才有的东西,比如任务队列。

1.1 构造方法

ThreadPoolExecutor有很多构造方法以及构造方式,最后都会调用一个七个参数的方法,参数的含义分别是:

  • int corePoolSize: 核心线程数
  • int maximumPoolSize: 最大线程数
  • long keepAliveTime & TimeUnit unit: 最大空闲时间
  • BlockingQueue workQueue: 工作队列,是一个阻塞队列
  • ThreadFactory threadFactory: 线程工厂,可以给线程起一个有意义的名字,也可以对线程进行扩展,比如给线程设置UncaughtExceptionHandler;
  • RejectedExecutionHandler handler: 拒绝策略,当上面的工作队列,是一个有界队列的时候,如果队列已经满了的场景的执行策略

ThreadPoolExecutor已经提供了四种拒绝策略:

  • CallerRunsPolicy:提交任务的线程自己去执行该任务。
  • AbortPolicy:默认的拒绝策略,会throws RejectedExecutionException。
  • DiscardPolicy:直接丢弃任务,没有任何异常抛出。
  • DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列

另外,ThreadPoolExecutor还有一个allowCoreThreadTimeOut(boolean value)方法,这个参数设置为TRUE,corePoolSize的线程空闲超时也会回收。如果线程池长期闲置,可以设置为TRUE。

一般情况下,在初始化的时候,对应的属性就已经指定了,不过,ThreadPoolExecutor也提供了创建后指定属性的方法,这些方法也有对应的get方法。可以利用这些方法实现对线程池的监控及动态修改。

  • setCorePoolSize 设置指定核心线程数
  • setMaximumPoolSize 设置最大线程数
  • setKeepAliveTime 设置最大空闲时间
  • setThreadFactory 设置线程工厂
  • setRejectedExecutionHandler 设置拒绝策略

对应任务队列只有一个get方法(getQueue),不过这个方法,目的仅仅是为了获取任务队列用于调试或监控,不应该随意更改这个队列。

1.2 线程池中线程的生命周期

1.2.1 线程的创建 prestartCoreThread & prestartAllCoreThreads

线程池中的线程,是延迟初始化的,提交任务才会触发线程的创建。也可以直接调用prestartCoreThread(启动一个核心线程)或者 prestartAllCoreThreads(启动所有核心线程),来提前启动线程。注意如果在ThreadPoolExecutor的构造时,直接使用了不为空的等待队列,则必须调用prestartCoreThread或prestartAllCoreThreads来启动线程,消费任务。

1.2.2 提交任务 execute & submit

当一个任务提交到线程池中:

  • 当前运行线程数,少于corePoolSize,即使存在空闲线程,也会创建新的线程,来处理任务;
  • 当前运行线程数,大于等于corePoolSize,任务队列没有满,则进入等待队列;
  • 当前运行线程数,大于等于corePoolSize,任务队列已满,但是运行线程数小于和maximumPoolSize,创建新线程,处理任务;
  • 否则,只能执行拒绝策略了;

1.2.3 线程空闲

当线程池中线程空闲时间(也就是在阻塞队列中等任务的时间)超过设置的空闲时间,如果此时线程数量大于corePoolSize,则线程会退出,否则不会退出。除非调用allowCoreThreadTimeOut(true),使线程数小于等于核心线程数,也会退出。注意,源码中并没有把某个运行的线程标记为核心线程或者非核心线程,而只有线程数量的概念。

1.2.4 线程池的停止 shutdown & shutdownNow

ThreadPoolExecutor提供了两种线程停止的方式

  • shutdown 不再接受新的任务,直到所有任务执行完毕,线程退出;
  • shutdownNow 不再接受新的任务,删除任务队列中的所有任务,并给所有线程发中断,线程空闲,自然就退出了,正在执行任务,如果任务响应中断,也会退出,否则能任务执行完毕,线程退出。

1.2.5 获取线程状态

ThreadPoolExecutor有几个获取当前状态的方法

  • isShutdown 是否shutdown(!RUNNING)
  • isTerminating 是否正在停止(状态在 RUNNING 和 TERMINATED 之间)
  • isTerminated 是否已经停止(TERMINATED)

二、源码解析

2.1 状态

ThreadPoolExecutor使用一个AtomicInteger的前3位,表示线程池的状态,后29位表示线程数。

// 接收新任务,并且处理任务队列中的任务,初始状态
private static final int RUNNING    = -1 << COUNT_BITS;
//不接收新任务,但是处理任务队列的任务
private static final int SHUTDOWN   =  0 << COUNT_BITS;
//不接收新任务,不处理任务队列,同时中断所有进行中的工作线程
private static final int STOP       =  1 << COUNT_BITS;
//所有任务已经被终止,工作线程数量为 0,到达该状态会执行terminated()
private static final int TIDYING    =  2 << COUNT_BITS;
//terminated()已经完成
private static final int TERMINATED =  3 << COUNT_BITS;

此处常量的大小是有顺序关系的,只能从前转移到后面,这样便于状态的比较,软件设计中经常用到。

状态转换

线程池状态之间的转换

  • RUNNING -> SHUTDOWN:shutdown()被调用
  • (RUNNING or SHUTDOWN) -> STOP:shutdownNow()被调用
  • SHUTDOWN -> TIDYING:队列和池均为空
  • STOP -> TIDYING:池为空
  • TIDYING -> TERMINATED:钩子方法terminated()已经完成。

🤔getPoolSize 为什么是获取HashSet workers 的长度,而不是state的后29位

因为state的后29位并不一定是真实的线程池的数量。后面会看到,在创建新线程前,会使用CAS的方式,先增加线程计数,把创建线程的资格占用住,然后再创建线程。甚至使用ThreadFactory创建线程还可能失败,后续还要减去state中的计数。

🤔所以,为什么非要用一个AtomicInteger来表示,而不是两个?

因为线程的状态的修改与线程数量的修改,是有冲突的,比如线程池状态从RUNNING 变为 SHUTDOWN,这时候就不应该增加Worker了,如果增加线程与状态修改并发了,一个原子操作,就只有一个能成功,不需要使用锁。

2.2 execute

    if (command == null)
        throw new NullPointerException();
    //1. 如果当前Worker数小于corePoolSize,调用addWorker(cmmond,true)新建核心线程处理该任务,addWorker也会检查状态和当前线程数量,如果添加失败(状态不对或线程数>=corePoolSize),返回false,添加成功,直接返回
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //2.当前Worker数大于等于corePoolSize,线程池还在运行,把任务添加到队列中。
    if (isRunning(c) && workQueue.offer(command)) {
        //2.1 即使添加成功,也要重新检查,毕竟是并发的,可能就这个间隙,线程池的状态就变了。
        //2.1.1 线程池是否关闭
        //2.1.2 是否需要新增一个线程 
        int recheck = ctl.get();
        if (!isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //3.无法入队列,添加非核心线程处理任务,添加失败,则拒绝任务。
    else if (!addWorker(command, false))
        reject(command);

🤔这段代码是什么意思

else if (workerCountOf(recheck) == 0) addWorker(null, false);

这段代码的含义是,如果当前队列的Worker数量为0,则增加一个没有任务的Worker。很好理解,这段代码之前的代码,向队列中添加任务成功了,却没有Worker去执行,当然需要补一个Worker进去。

🤔可是问题是,什么情况下,会出现这种场景呢?

应该是一个及其特殊的场景。

  • 任务提交,正在进入阻塞队列, workQueue.offer(command)
  • 所有的Worker满足了退出条件,正在退出

也就是任务刚进入队列,所有的worker都撤了。

  1. 要设置corePoolSize=0或者设置allowCoreThreadTimeOut为true。worker空闲了足够时间;
  2. 当前状态不是RUNNING,并且remove(command)失败,也就是对应上一句的IF。不过讲道理,如果remove失败了,应该是被Worker给take走了,worker数量也不会是空,除非阻塞队列有问题。
  3. 或者上一步addWorker时创建线程直接失败了,比如线程工厂抛出异常。

2.3 addWorker

2.3.1 概述

execute在多处调用了addWorker,用于添加Worker中。

方法签名

    /**
     * Checks if a new worker can be added with respect to current
     * pool state and the given bound (either core or maximum). If so,
     * the worker count is adjusted accordingly, and, if possible, a
     * new worker is created and started, running firstTask as its
     * first task. This method returns false if the pool is stopped or
     * eligible to shut down. It also returns false if the thread
     * factory fails to create a thread when asked.  If the thread
     * creation fails, either due to the thread factory returning
     * null, or due to an exception (typically OutOfMemoryError in
     * Thread.start()), we roll back cleanly.
     *
     * 添加一个以firstTask为第一个任务的Worker到线程池。core标识添加的是否是核心线程。添加失败返回false。
     */
    private boolean addWorker(Runnable firstTask, boolean core) 

🤔什么场景会添加失败

  • 线程池状态不是Running
  • 线程数量到达上限,参数core为true时,上限是corePoolSize,false时,上限是maximumPoolSize。
  • ThreadFactory创建线程失败
  • 线程启动异常(比如start的时候抛出OutOfMemoryError)

2.3.2 源代码

    // 1.尝试增加线程计数,注意第一段代码,仅仅是CAS增加计数,相当于在获取一个“资格”,如果超出线程数量的边界(core ? corePoolSize : maximumPoolSize),则直接退出,增加失败
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // 1.1 状态不对,直接返回false。除非状态处于SHUTDOWN且队列中有任务且firstTask==null(对应前文addWorker(null, false))。
        // 总结一下:线程池没有运行,直接返回false,除非处于SHUTDOWN状态且队列里还有任务待执行。
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
            return false;
        // 1.2 自旋+CAS的方式,增加workerCount,数量超过上限则直接返回false。
        for (;;) {
            int wc = workerCountOf(c);
            // CAPACITY是2的29次方减1,线程池状态使用int的后29位存储线程数量,也就是线程数量理论最大值
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // cas成功,跳出retry循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            //  因为状态改变导致CAS失败,重新进入外层循环,重新进行状态判断
            if (runStateOf(c) != rs)
                continue retry;
            // 否则是因为修改线程数量失败,重新尝试内层循环即可。
        }
    }
    
    // 2. 增加一个Worker,加锁后放入workers(非线程安全的HashSet)
    //    + 成功则启动线程
    //    + 失败,调用addWorkerFailed,回退状态。
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);//这里创建了线程
        final Thread t = w.thread;
        if (t != null) {
            // 锁,需要同步处理workers(非线程安全的HashSet)
            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();
            //2.1 添加成功,调用Thraed.start
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        // 2.2 添加失败,回退状态
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;

🤔使用ReentrantLock是为了保护什么资源

为了保护记录Worker的hashset——workers,HashSet是非线程安全的,所有关于HashSet的操作——增加Worker/删除Worker/获取size/遍历workers——都需要加锁。

🤔为什么使用存放线程的workers使用非线程安全的HashSet?

狗哥的注释

    /**
     * Lock held on access to workers set and related bookkeeping.
     * While we could use a concurrent set of some sort, it turns out
     * to be generally preferable to use a lock. Among the reasons is
     * that this serializes interruptIdleWorkers, which avoids
     * unnecessary interrupt storms, especially during shutdown.
     * Otherwise exiting threads would concurrently interrupt those
     * that have not yet interrupted. It also simplifies some of the
     * associated statistics bookkeeping of largestPoolSize etc. We
     * also hold mainLock on shutdown and shutdownNow, for the sake of
     * ensuring workers set is stable while separately checking
     * permission to interrupt and actually interrupting.
     */

大概的意思是,主要是为了防止在interruptIdleWorkers的时候,造成中断风暴,如果多个线程同时调用shutdown,会调用interruptIdleWorkers给所有的线程发送中断,如果是线程安全的set,则会同时进行,造成中断风暴。而使用锁,则是串行的,后面执行的interruptIdleWorkers时,会判断是否已经中断,已经中断的,不会再次中断。

🤔addWorkerFailed都做了什么

其实,就是相当于回滚,删除了workers中的Worker,并减去state中的线程数。并顺便调了一下tryTerminate。至于tryTerminate,后面再说。

2.4 Worker

2.4.1 概述

ThreadPoolExecutor需要对线程进行一下封装,以实现:

  • 任务结束之后,线程不会退出,而是等待任务队列中的下一个任务;
  • 任务出现异常后,线程即使退出,也会启动一个新的线程替代他;
  • 空闲的线程,在一定条件下(超时或shutdown),可以退出;

ThreadPoolExecutor用一个内部类Worker实现了Runnable接口,同时,内部封装了一个Thread。另外Worker还继承了AbstractQueuedSynchronizer,实现了锁的功能。

🤔Worker继承了AbstractQueuedSynchronizer,有什么用

李狗哥的解释

    /**
     * Class Worker mainly maintains interrupt control state for
     * threads running tasks, along with other minor bookkeeping.
     * This class opportunistically extends AbstractQueuedSynchronizer
     * to simplify acquiring and releasing a lock surrounding each
     * task execution.  This protects against interrupts that are
     * intended to wake up a worker thread waiting for a task from
     * instead interrupting a task being run.  We implement a simple
     * non-reentrant mutual exclusion lock rather than use
     * ReentrantLock because we do not want worker tasks to be able to
     * reacquire the lock when they invoke pool control methods like
     * setCorePoolSize.  Additionally, to suppress interrupts until
     * the thread actually starts running tasks, we initialize lock
     * state to a negative value, and clear it upon start (in
     * runWorker).
     */

Worker简单的实现了AQS,并在任务的开始和结束,获取、释放锁。而在interruptIdleWorkers(中断空闲线程的方法)方法中,在中断线程前,会尝试获取锁,获取锁成功了,才会中断线程,也就保证了interruptIdleWorkers,不会给正在执行任务的Worker发中断。 interruptIdleWorkers,目的是中断空闲线程,也就是不能中断正在执行的线程。而这个锁,就是这个目的,防止interruptIdleWorkers中断正在执行任务的线程。

interruptIdleWorkers中断相关的代码

    if (!t.isInterrupted() && w.tryLock()) {
        try {
            t.interrupt();
        } catch (SecurityException ignore) {
        } finally {
            w.unlock();
        }
    }

interruptIdleWorkers会在停止线程和改变线程池的相关方法中调用

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

值得注意的是,shutdownNow并不会调用interruptIdleWorkers,而是interruptWorkers,而这个方法,是不管任务是否正在执行,都会发送中断。

🤔Worker为什么没有设计成可重入的,或者说,为什么没有直接使用ReentrantLock,而是继承AQS

在上面狗哥的注释已经说了,是不想Worker运行的任务,获取到了ThreadPoolExecutor的引用,调用ThreadPoolExecutor的相关方法(比如setCorePoolSize)获取到锁,而中断自己。更具体的例子就是,一个任务**(注意是任务本身)**,在运行的过程中,调用了运行自己的ThreadPoolExecutor.setCorePoolSize方法,而setCorePoolSize在一定条件下,会调用interruptIdleWorkers,如果可以重入,interruptIdleWorkers方法就会获取到锁,进而给这个线程发了中断。 setCorePoolSize中一小段调用interruptIdleWorkers方法的代码块:

    this.corePoolSize = corePoolSize;
    if (workerCountOf(ctl.get()) > corePoolSize)
        interruptIdleWorkers();

🤔Worker初始化的时候,为什么设置了AQS的状态为-1

Worker的构造方法

    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker 直到运行,才能中断
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

Worker在初始化的时候,会设置AQS的state的为-1,直到任务开始运行的时候,在runWorker的开始,才会unlock释放锁;这样就保证从创建到未运行的过程中,不能被中断。

🤔Worker初始化的时候,初始化状态为-1,在runWorker中调用unlock,调用release(1),能解锁成功么?

能,release(1)在AQS中,最后会调用tryRelease,而Worker中实现的TryRelease是这样的,一定能释放成功。

    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

2.4.2 Worker源代码

Worker实现了Runnable接口,ThreadPoolExecutor在addWorker时,会把Worker的Thread给start起来,线程中跑的就是Worker的run方法。Worker的run方法,调用的就是ThreadPoolExecutor的runWorker方法,看看李狗哥的注释:

    /**
     * Main worker run loop.  Repeatedly gets tasks from queue and
     * executes them, while coping with a number of issues:
     *
     * 1. We may start out with an initial task, in which case we
     * don't need to get the first one. Otherwise, as long as pool is
     * running, we get tasks from getTask. If it returns null then the
     * worker exits due to changed pool state or configuration
     * parameters.  Other exits result from exception throws in
     * external code, in which case completedAbruptly holds, which
     * usually leads processWorkerExit to replace this thread.
     *
     * 2. Before running any task, the lock is acquired to prevent
     * other pool interrupts while the task is executing, and then we
     * ensure that unless pool is stopping, this thread does not have
     * its interrupt set.
     *
     * 3. Each task run is preceded by a call to beforeExecute, which
     * might throw an exception, in which case we cause thread to die
     * (breaking loop with completedAbruptly true) without processing
     * the task.
     *
     * 4. Assuming beforeExecute completes normally, we run the task,
     * gathering any of its thrown exceptions to send to afterExecute.
     * We separately handle RuntimeException, Error (both of which the
     * specs guarantee that we trap) and arbitrary Throwables.
     * Because we cannot rethrow Throwables within Runnable.run, we
     * wrap them within Errors on the way out (to the thread's
     * UncaughtExceptionHandler).  Any thrown exception also
     * conservatively causes thread to die.
     *
     * 5. After task.run completes, we call afterExecute, which may
     * also throw an exception, which will also cause thread to
     * die. According to JLS Sec 14.20, this exception is the one that
     * will be in effect even if task.run throws.
	 */

runWorker方法,主要就是一个循环,不断的从任务对列取任务并执行。要处理下面这些问题:

  • 起动的时候,worker内可能有一个初始任务,这样就不用从任务队列中取任务了,其他时候,都会从任务队列中获取任务。如果取任务的时候,返回了null(可能因为ThreadPoolExecutor状态改或配置改变),线程退出。也有可能因为任务抛出异常,这时会调用processWorkerExit,重新创建一个线程替代这个退出的线程。
  • 跑任何任务之前,获取锁,防止任务执行的时候被中断,这个之前说过。
  • 执行任务之前,会调用beforeExecute,留给子类复写的方法,可能会抛出异常
  • beforeExecute正常执行完成后,执行任务,把RuntimeException,Error和Throwables都捕获,然后重新抛出去,捕获就是为了在finally中afterExecute去处理而已,afterExecute也是给子类来实现的。抛出的异常,由Thread的UncaughtExceptionHandler来处理。这里抛出异常,会导致当前线程退出,当然,如果ThreadPoolExecutor状态不是stop,还会再补一个新的Worker。因为没有办法抛出Throwables,所以使用Error包装了一下,再抛出。
  • 执行afterExecute。
2.4.2.1 runWorker

Worker的run方法,就是调用的ThreadPoolExecutor的runWorker方法:

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts,这个是解锁Worker初始化时的锁,如果Worker在初始化时,不加锁,而接收到了interruptIdleWorkers的中断,在下面getTask会触发该中断
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();//上锁,防止运行时被interruptIdleWorkers中断
                // 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
                // 保证在状态在STOP之前,清空中断,之后,一定有中断,注意这里的recheck。
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);//before,留给子类复写
                    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);//Throwable不能抛,所以封装一下
                    } finally {
                        afterExecute(task, thrown);//after,也是留给子类复写
                    }
                } finally {
                    task = null;
                    w.completedTasks++;//计数
                    w.unlock();
                }
            }
            //Abruptly:突然的,如果是因为异常退出,不会执行到这里,completedAbruptly=true;
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

runWorker是一个循环,循环的条件是: task != null || (task = getTask()) != null) 每次循环结束的finally中,都会把task设置为null,task!=null只有在初次运行,且firstTask!=null时才成立,其余都要运行getTask方法,也就是从任务队列中取任务。当getTask返回null的时候,Worker就会退出,也就是说,Worker在什么时候退出,是在getTask中控制的。

2.4.2.2 getTask

狗哥的注释:

    /**
     * Performs blocking or timed wait for a task, depending on
     * current configuration settings, or returns null if this worker
     * must exit because of any of:
     * 1. There are more than maximumPoolSize workers (due to
     *    a call to setMaximumPoolSize).
     * 2. The pool is stopped.
     * 3. The pool is shutdown and the queue is empty.
     * 4. This worker timed out waiting for a task, and timed-out
     *    workers are subject to termination (that is,
     *    {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
     *    both before and after the timed wait, and if the queue is
     *    non-empty, this worker is not the last thread in the pool.
     *
     * @return task, or null if the worker must exit, in which case
     *         workerCount is decremented
     */

理解: getTask以阻塞的方式从任务队列中获取任务,是否设置超时时间,根据ThreadPoolExecutor的配置和当前的Worker数量决定。getTask返回null,有以下几种场景:

  • setMaximumPoolSize方法被调用,并且当前Woker数量大于新的maximumPoolSize;
  • ThreadPoolExecutor停止了
  • ThreadPoolExecutor被shutdown且任务队列是空的
  • 获取任务的时候等待超时,并且当前Worker可以退出(具体要看当前Woker数量和CorePoolSize/allowCoreThreadTimeOut的配置)

源代码走起:

    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. 停止了或者被shutdown并任务队列是空的,返回null
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            // wc > maximumPoolSize或者超时且满足allowCoreThreadTimeOut || wc > corePoolSize(就是上面那行代码),返回null
            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;
            }
        }
    }

理解了getTask是做什么的,看代码就非常容易了。还要说的一点是,之前一直提到的interruptIdleWorkers,就是interrupt这里,从runWorker中可以看到,这里是不会加锁的,并且,从任务队列中阻塞的获取任务,是可以响应中断的,会抛出InterruptedException异常,然后getTask会执行timedOut = false;这行代码,标识不是超时。在新的一次循环中,在这段代码中,就可能会返回null,达到worker退出的目的:

    // Check if queue empty only if necessary. 停止了或者被shutdown并任务队列是空的,返回null
    if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
        decrementWorkerCount();
        return null;
    }
2.4.2.3 processWorkerExit

runWorker跳出循环后(可能是正常退出,也可能是抛出异常后退出,由completedAbruptly参数标识),会执行processWorkerExit方法。这个方法会把这个worker从workers这个hashSet中移除。在一定条件下(比如异常退出),还会再补一个线程。

源代码:

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        //getTask在返回null前,就会调用decrementWorkerCount方法,而异常退出则不会,所以这里要补一下
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        // 从workers中删除
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
        // 尝试退出,后面会说这个方法
        tryTerminate();
        // 异常退出或者没有worker了且任务队列不为空,会补一个worker
        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);
        }
    }

2.5 线程池关闭 shutDown & shutDownNow

关闭线程池,可以使用shutDown和shutDownNow

  • shutDown 不接受新的任务,但之前提交的任务都会执行完毕。会给所有空闲的线程发送中断,以让他们退出。
  • shutDownNow 不接受新的任务,并且,还在任务队列中的任务也会被移除(并且作为返回值返回),并且给所有Worker发送中断。

看下shutDown的代码:

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();//这里要遍历workers设置权限,要加锁
            advanceRunState(SHUTDOWN);//改状态
            interruptIdleWorkers();//这里之前说过,为了防止不必要的interrupt,使用ReentrantLock锁住。
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

代码没几行,先设置权限,改状态为SHUTDOWN,给空闲线程发中断,调个子类方法,最后tryTerminate。除了最后的tryTerminate,其余都很好理解。

同样,shutDownNow也非常简单,不过是interruptIdleWorkers改为interruptWorkers,强行给所有worker发中断。

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

tryTerminate

tryTerminate,从名字就可以看出来,是尝试终止,这要从ThreadPoolExecutor如何终止说起。其实无论shutdown还是shutDownNow,都无法立即终止ThreadPoolExecutor。即使shutDownNow,如果有正在运行的任务,也不过是给他们发中断,任务不一定会立即停止(甚至都不会响应中断)。想要确切的得知ThreadPoolExecutor何时停止,只能等,也就是调用awaitTermination方法,去等着ThreadPoolExecutor结束。

🤔那到底什么时候停止呢?

没有什么太好的办法,只能在所有可能会停止的地方,加上tryTerminate,判断ThreadPoolExecutor是否停止了,停止了,就更改状态,并通知所有awaitTermination等待的线程。

调用了tryTerminate的方法:

  • ThreadPoolExecutor.addWorkerFailed(Worker)
  • ScheduledThreadPoolExecutor.onShutdown()
  • ThreadPoolExecutor.processWorkerExit(Worker, boolean)
  • ThreadPoolExecutor.purge()
  • ThreadPoolExecutor.remove(Runnable)
  • ThreadPoolExecutor.shutdown()
  • ThreadPoolExecutor.shutdownNow()
目的

tryTerminate方法的目的就是,在一定条件下(SHUTDOWN并且任务队列&&workers都是空 || STOP并且workers都是空),把线程池的状态改为TERMINATED,并通知awaitTermination等待的线程。当状态>=SHUTDOWN,且没有任务的时候,终止空闲线程。

🤔问题来了,什么场景下,状态>=SHUTDOWN,没有任务了,却还有空闲线程!!!

在shutDown时,正在执行任务的worker,是不会被中断的,当多个worker执行任务完成后,调用getTask方法,走到了下面这几行代码:

// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    decrementWorkerCount();
    return null;
}

假设有10个worker都在这里并发了,而任务队列中还有5个任务。那么这里都能通过,但是在真正获取任务的时候,只有5个能够获取到,继续执行任务,另外五个,没有收到中断,则会一直阻塞到take上,也就是空闲状态。这就是在SHUTDOWN状态下,还有空闲线程的场景。还记得么,每个线程退出的时候,都回调用processWorkerExit方法,而这个方法调用了tryTerminate,会给这些空闲线程发中断,让他们退出。

tryTerminate源码:

	final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) || //正在运行,直接返回
                runStateAtLeast(c, TIDYING) ||状态是TIDYING以后,说明其他线程tryTerminate正在运行,退出
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))//任务队列中还有任务,退出
                return;
            //这里在中断空闲线程
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }
            //修改状态,并通知等待线程,话说真的有必要自旋么,还是CAS习惯性写法
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        terminated();
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }
awaitTermination

没啥好说的,使用condition.await实现的。在tryTerminate中signalAll的。