ThreadPoolExecutor线程池源码分析

403 阅读20分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


前言:

为什么要使用线程池,有哪些好处?

我们先来看看,如果每个任务都创建一个线程,会有什么问题:

  • 反复创建线程系统开销比较大,每个线程创建和销毁都需要时间,如果任务比较简单,就有可能导致创建和销毁线程消耗的资源比线程执行任务本身消耗的资源还要大
  • 过多的线程会占用过多的内存等资源,还会带来过多的上下文切换,同时还会导致系统不稳定

通过线程池解决以上问题:

  • 针对反复创建线程开销大的问题,线程池用一些固定的线程一直保持工作状态并反复执行任务
  • 针对过多线程占用太多内存资源的问题,线程池会根据需要创建线程,控制线程的总数量,避免占用过多内存资源

一、原理浅析:

1、运行机制

我们先通过一个具体例子来窥探线程池的内部运行机制

假如说,我们将线程池看作一个仓库,corePoolSize 核心线程数就是仓库的长工(一年四季都在仓库干活),起初仓库量小,自己就可以搞定,也就还没雇长工来帮忙,后面开始扩展规模了,开始雇长工来干活。

为节省开支,预定了最多可雇的长工人数(corePoolSize), 这些个长工在平常不是特别忙的时候都能轻松搞定手里的活。一遇到仓库量特别大的时候就应付不过来了,慢慢的仓库的包裹开始堆积起来(放进workQueue)。

你想我不可能货量稍微一大就招零时工,这招人还得耗资源呢,何况如果就这一两天量大一点,之后又回归平常呢?这种情况我还不如先把活堆积一些,这两天让这些长工慢慢消化,这样的方式耗费的资源更少(更节约成本)。

但是这个活它不能一直堆积吧,我得预设置一个量(队列大小),达到了这个量我就认为确实可能会爆仓,开始雇零时工了帮忙处理了。

但是吧,这个临时工他也得有个量(maximumPoolSize - corePoolSize)才行,毕竟仓库就这么大(想想操作系统资源也是有限的),所以临时工也不能太多。

仓库爆仓情况下的总量 = 所有长工处理量之和 + 总的积压量 + 所有零时工处理量之和。

如果超过这个量还有包裹源源不断的投递过来,对不起!爆仓了,已经处理不了了;这个时候就可以采用一些指定的拒绝策略来处理(RejectedExecutionHandler)。

高峰期一过,仓库货量又回到长工就完全可以处理的级别了,考虑在这些临时工空闲时间超过(keepAliveTime)就辞退,慢慢得仓库的工人量就维护在 corePoolSize 和 maximumPoolSize 之间。

2、线程创建时机

在这里插入图片描述 当提交任务后,线程池首先会检查当前线程数,如果此时线程数小于核心线程数,比如最开始线程数量为 0,则新建线程并执行任务,随着任务的不断增加,线程数会逐渐增加并达到核心线程数。

此时,如果仍有任务被不断提交,就会被放入 workQueue 任务队列中,等待核心线程执行完当前任务后重新从 workQueue 中提取正在等待被执行的任务。

当任务队列被放满时,将尝试创建非核心线程继续处理。最坏的情况下,非核心线程也达到上限时,将采用拒绝策略直接拒绝。

3、拒绝时机

第一种情况是当我们调用 shutdown 等方法关闭线程池后,即便此时可能线程池内部依然有没执行完的任务正在执行,但是由于线程池已经关闭,此时如果再向线程池内提交任务,就会遭到拒绝。

第二种情况是线程池没有能力继续处理新提交的任务,也就是工作已经非常饱和的时候。

4、拒绝策略

AbortPolicy: 这种拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException 的 RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略。

DiscardPolicy: 当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。

DiscardOldestPolicy: 如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险。

CallerRunsPolicy: 当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务

二、源码分析

1、ThreadPoolExecutor构造方法

    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 + 时间单位: 空闲线程的存活时间

  • workQueue: 存放任务的队列

  • threadFactory:线程工厂, 用于创建新的线程

  • handler:拒绝策略处理

2、线程池运行状态

五种运行状态

  • RUNNING: 接收新的任务并处理队列里的任务
  • SHUTDOWN: 不接收新的任务,但是队列中任务会执行
  • STOP: 不接收新的任务,不处理队列中的任务,并终止正在处理的任务
  • TIDYING: 所有任务都被终止的,同时工作线程数量为0
  • TERMINATED: terminated() 方法执行结束后会进入这一状态,表示线程池已关闭

threadPoolExecutor 中以 AtomicInteger 类型的变量 ctl 高 3 位存储线程池状态,低 29 位存线程池 worker 数量:

    // RUNNING: 十进制:-536870912  二进制:11100000000000000000000000000000
    private static final int RUNNING    = -1 << COUNT_BITS; 
    // SHUTDOWN: 十进制:0  二进制:0
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    // STOP: 十进制:536870912  二进制:00100000000000000000000000000000
    private static final int STOP       =  1 << COUNT_BITS;
    // TIDYING: 十进制:1073741824  二进制:01000000000000000000000000000000
    private static final int TIDYING    =  2 << COUNT_BITS;
    // TERMINATED: 十进制:1610612736  二进制:01100000000000000000000000000000
    private static final int TERMINATED =  3 << COUNT_BITS;
    
    // COUNT_BITS: 29
    private static final int COUNT_BITS = Integer.SIZE - 3;

线程池状态判断相关操作

// 判断当前线程池运行状态值是否小于给定值
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;
}    

3、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);
            // 重新检查的时候发现工作线程数量等于0,新创建一个线程
            // 这里第一个参数firstTask为null,是因为该任务已经通过offer添加至队列中了    
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 尝试创建非核心线程处理任务
        else if (!addWorker(command, false))
            reject(command);
    }    

以上主要就是三步

  • 如果工作线程数量小于核心线程数(corePoolSize),就创建新线程处理任务
  • 如果工作线程数量大于等于核心线程数,尝试将任务放进队列中
  • 如果队列满了,尝试创建非核心线程处理任务,前提是工作线程总量小于maximumPoolSize
  • 如果以上都不满足,执行拒绝策略

为什么需要二次检查线程池的运行状态?

如果一个任务成功加入任务队列,我们仍需要二次检查是否需要添加一个工作线程, 因为所有存活的工作线程有可能在最后一次检查之后已经执行结束或者执行当前方法的时候线程池是否已经 shutdown 了。

所以我们需要二次检查线程池的状态,如果已经没有可用的线程,就创建一个新的线程(worker), 需要注意的是第一个参数传的是 null, 因为这个任务已经在上一步通过 offer 加到了队列中

3.1、addWorker

这里正常情况下会创建 worker (新线程)去处理新任务

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            // 运行状态
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            // 分两种情况考虑:
            // 1、如果线程池状态>SHUTDOWN,即(STOP,TIDYING,TERMINATED)中的一个,直接返回false(因为这些条件下要求 清空队列,正在运行的任务也要停止)
            // 2、如果线程池状态=SHUTDOWN & (firstTask!=null || 队列为空)直接返回false, 
            // 换言之,在SHUTDOWN状态下, 想要创建一个firstTask为空的新worker,需要确保队列不为空(队列为空就意味着这个新的worker暂时还没有任务可以执行,所以也没有创建worker的必要, 因为这个worker是用来消化队列中的任务)
            // 大部分情况下,我们需要新创建的worker的firstTask都有初始化任务, 在SHUTDOWN状态下,就不允许在创建worker了,直接返回false
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                // 计算worker的数量
                int wc = workerCountOf(c);
                // 如果worker数量已经超过指定大小,则不允许创建
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 尝试cas累加worker的数量,如果成功就跳出最外层循环    
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // 本次cas失败,表明ctl已经变了,检查线程池状态,如果状态变了就跳到最外层重新执行一次
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
                // 如果到这里了说明是worker的数量改变了导致的cas失败,那就在内层自旋操作 再来一次
            }
        }

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

                    // 状态rs < SHUTDOWN表明是RUNNING状态
                    // 如果线程池的状态是SHUTDOWN,那么创建的worker是用来处理队列中的任务,因此需要满足firstTask == null
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        // 如果线程提前start则认为是异常状态
                        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运行
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                // 如果添加worker失败了,这里需要处理一下
                addWorkerFailed(w);
        }
        return workerStarted;
    }  

当创建新 worker 时,firstTask 为 null 的含义:

  • 首先需要知道的是 worker 执行的任务的来源,一是初始化时的 firstTask 即当前 worker 的第一个任务, 二是从队列里取
  • firstTask 表示当前 worker 初始化任务,也就是第一个要执行的任务;如果firstTask = null, 说明此 worker 只从队列中取任务来执行
  • 所以当创建 firstTask 为 null 的 worker 时,只有队列不为空才有创建的必要,因为目的是去消化队列中的任务

分三种情况来看创建 worker:

  • 正常情况下线程池的状态是 RUNNING,这个时候只需根据 corePoolSize 或者 maximumPoolSize 来判断是否应该创建新的woker
  • 如果是 STOP,TIDYING,TERMINATED 状态,表明线程池处于清理资源,关闭线程池(清空队列,终止正在运行的任务,清空 worker),这个时候不允许创建新的 worker
  • SHUTDOWN 状态,此状态比较特殊,因为在此状态会继续处理队列中的任务,但是不允许往队列中新增任务,同时正常处理的任务也会继续处理; 此状态下,在 firstTask 为 null 并且队列不为空的情况下可以创建新的worker 来处理队列中的任务,其他情况是不允许的。

3.2 addWorkerFailed

新增 worker 失败后的处理

    private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (w != null)
                workers.remove(w);
            // worker计数器减1    
            decrementWorkerCount();
            // 尝试终止线程池
            tryTerminate();
        } finally {
            mainLock.unlock();
        }
    }   

4、worker

线程池中处理任务的个体,worker 继承 AbstractQueuedSynchronizer,实现了非重入锁

  • state=0, 表示锁未被持有
  • state=1, 表示锁被持有
  • state=-1, 初始化的值,防止worker线程在真正运行task之前被中断,ThreadPoolExecutor.Worker#interruptIfStarted
    // 注意看,Worker是实现了Runnable接口,说明我可以把一个worker扔到thread里面,然后调用thread.start(),
    // 就可以处理当前worker的逻辑(入口是重写的Runnable的run方法)
    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
    
        // 每个worker中都会封装一个真正用于处理任务的线程
        final Thread thread;
        // 这个worker初始化的时候分配的首个任务
        Runnable firstTask;
        // 记录当前worker处理完的任务数量
        volatile long completedTasks;

        // 构造方法
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            // 使用ThreadFactory创建线程
            // 这里this是当前实现了Runnable接口的Worker对象,也就是说当我调用thread.start()方法时,就会调用worker的入口方法run
            this.thread = getThreadFactory().newThread(this);
        }
        
        // 基于AQS实现的非重入锁
        protected boolean isHeldExclusively() {
             return getState() != 0;
         }
 
         // 实际并未使用传入的参数
         // state=0表示可以获取,将state设置为1表示获取成功
         protected boolean tryAcquire(int unused) {
             if (compareAndSetState(0, 1)) {
                 setExclusiveOwnerThread(Thread.currentThread());
                 return true;
             }
             return false;
         }
 
         // 实际并未使用传入的参数
         // 将state设置为0表示锁释放成功
         protected boolean tryRelease(int unused) {
             setExclusiveOwnerThread(null);
             setState(0);
             return true;
         }
 
         public void lock()        { acquire(1); }
         public boolean tryLock()  { return tryAcquire(1); }
         public void unlock()      { release(1); }
         public boolean isLocked() { return isHeldExclusively(); }        

        // worker的主要入口逻辑
        public void run() {
            runWorker(this);
        }

4.1、runWorker

worker运行的主要逻辑

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        // 和setState(-1); 相对应
        // 这个时候state=0了,也就可以被中断了,(ThreadPoolExecutor.Worker#interruptIfStarted)
        w.unlock(); // allow interrupts
        // 是否有异常产生
        boolean completedAbruptly = true;
        try {
            // task的来源要么是firstTask,要么是队列
            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
                // 如果线程池是STOP及之后的状态,需要确保它是中断的
                // 如果是STOP之前的状态,就要确保它不能被中断(如果有的话就要清除中断标志,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 {
            // 处理worker退出
            //  一种是异常退出
            //  一种是普通线程(非核心),空闲时间到了退出
            processWorkerExit(w, completedAbruptly);
        }
    }       

涉及相关流程

  • task 的来源要么是 firstTask,要么是队列
  • getTask 这个操作是有可能阻塞的,在阻塞情况下,实际上是并未获取到锁资源;因此其他地方可以通过能否获取到锁资源来判断当前线程是否处于空闲状态, 从而对 worker 作出诸如中断类型的操作(jThreadPoolExecutor#interruptIdleWorkers(boolean))
  • 需要注意的是: 如果 getTask 取的 task==null, 要么是线程池 STOP了,要么是非核心线程空闲超时 需要清理了
  • task.run(), 执行此用户任务时,如果遇到异常,会跳到finally块中processWorkerExit 进行 worker 退出处理,这个时候参数completedAbruptly=true, 表明用户task抛出了异常
  • 线程中断标志处理
    • 如果线程池是STOP及之后的状态,需要确保它是中断的(wt.interrupt()设置中断标志);这些状态下,表明线程池不会处理任何task,正清理线程池的worker和队列等
    • 如果是STOP之前的状态,就要确保它不能被中断(如果有的话就要清除中断标志,Thread.interrupted会清除中断标志);比如SHUTDOWN状态,还是会处理线程池目前存在的task信息

4.2 getTask

从队列中取任务,会根据线程池目前状态来决定是否需要执行取task操作

    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.
            // 两种情况从队列里面取不到task
            // 1、线程池是STOP状态,这个状态会清空队列,同时停止正在处理的任务,自然,如果是这个状态,直接返回null,表明取不到task
            // 2、线程池是SHUTDOWN状态并且队列为空,因为此状态下 队列里是不会新增任何task,所以在队列为空的情况下,自然也是取不到
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                // worker数量减一
                decrementWorkerCount();
                return null;
            }

            // 计算worker数量
            int wc = workerCountOf(c);

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

            // 通过最大线程数量或者获取task超时 来决定是否要消减此worker
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                // 1、timed期限的,非核心线程如果空闲时间超过keepAliveTime,就会被清理掉
                // 因此如果这里从阻塞队列里在keepAliveTime时间内都没有取到task,说明处理超时了
                // 2、没有timed限制的,take阻塞的取task
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

主要操作:

  • 两种情况从队列里面取不到task
    • 线程池是STOP及STOP之后的状态,这个状态会清空队列,同时停止正在处理的任务,自然,如果是这个状态,直接返回null,表明取不到task
    • 线程池是SHUTDOWN状态并且队列为空,因为此状态下 队列里是不会新增任何task,所以在队列为空的情况下,自然也是取不到
  • 通过最大线程数量或者获取task超时 来决定是否要清理此worker
  • 从队列中取task有超时限制的情况 - timed
    • 线程数量大于corePoolSize
    • allowCoreThreadTimeOut 设置了核心线程超时销毁参数(如果设置了此参数,在核心线程空闲时间超过了keepAliveTime之后,便会销毁此核心线程)
  • 根据是否有timed限制从阻塞队列里面取task,如果timed=true 则有可能达到了超时时间也取不到task

4.3、processWorkerExit

对worker执行结束之后,清理掉当前worker之后考虑是否采取用新的worker来替换

    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 {
            // 当前worker完成的task数量累加
            completedTaskCount += w.completedTasks;
            // 从woker Set中移除当前worker
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        // 每次worker结束后都要尝试终止线程池,说不定某个时刻worker都被清理了并且达到了线程池终止的条件
        // 就可以从这里结束
        tryTerminate();

        int c = ctl.get();
        // 如果线程池状态小于STOP
        if (runStateLessThan(c, STOP)) {
            // 如果不是异常
            // 如果用户任务发生了异常,尝试替换worker
            // 或者核心线程数量小于corePoolSize,尝试添加worker
            // 或者队列非空,但是已经没有worker了,尝试添加worker
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            
            // 这里新添加worker,没有初始化的firstTask, 这里worker处理的任务来自于队列
            addWorker(null, false);
        }
    }   

主要流程:

  • 从 worker set 中移除当前 worker
  • 尝试终止线程池
  • 如果线程池状态还未达到 STOP,则可以根据以下情况添加新的 worker
    • 用户task抛出了异常(也就是 completedAbruptly=true)
    • 运行的线程数已经小于核心线程数
    • 运行的线程数为0,但是队列中的任务不为空

4.4 tryTerminate

尝试终止线程池,满足以下几个条件之一即可终止

  • 线程池处于 SHUTDOWN 状态,worker 线程池为空并且队列为空
  • 线程池处于 STOP 状态,worker 线程池为空
    final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            // 如果线程池是RUNNING状态的 不管
            // 如果线程池是TIDYING、TERMINATED状态的不管(基本已经处于关闭状态了)
            // 如果线程池是SHUTDOWN状态并且队列中还有task(SHUTDOWN状态下还是会处理现有的task)
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            
            // 到这里就说明需要开始线程池终止操作  
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // 操作1:设置线程池状态为TIDYING,worker数量为0
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        // terminated钩子方法
                        terminated();
                    } finally {
                        // 操作1设置后,又处理了terminated方法,那么就可以把线程池置为TERMINATED状态了(线程池已完全关闭)
                        ctl.set(ctlOf(TERMINATED, 0));
                        // 通知在termination条件上等待的操作
                        // 比如awaitTermination方法的等待操作,这里唤醒后,可能会提前结束等到操作
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

以下几个条件存在其中一个表明不可终止线程池

  • 如果线程池是 RUNNING 状态的
  • 如果线程池是 SHUTDOWN 状态并且队列中还有 task(SHUTDOWN状态下还是会处理现有的task)

5、优雅的关闭的线程池

通常操作如下:

    private void stop() throws Exception {
        executor.shutdown();
        executor.awaitTermination(200, TimeUnit.MILLISECONDS);
        executor.shutdownNow();
    }

5.1、shutdown 关闭线程池

线程池中,在调用 shutdown 方法之前提交的task都会处理,后面新提交的task 将不会处理,如果是 shutdown 状态多次调用也不会产生任何影响

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 如果有security管理,需要确保有权限去shutdown线程池
            checkShutdownAccess();
            // 设置线程池状态为SHUTDOWN
            advanceRunState(SHUTDOWN);
            // 中断所有空闲线程
            interruptIdleWorkers();
            // 为ScheduledThreadPoolExecutor提供的钩子方法
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        
        // 尝试终止线程池
        tryTerminate();
    }

此方法告诉线程池,现已接收的任务继续处理完,新来的任务一概不接收;是一种比较友好的关闭线程池的方式,不会丢弃已存在和正在处理的任务

线程池关闭 shutdown() 与步骤

  • 先将线程池的状态改为 SHUTDOWN
  • 尝试中断所有空闲线程
  • 尝试终止线程池

5.1.1、interruptIdleWorkers

中断所有空闲状态下的线程

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

5.2、awaitTermination

阻塞等待

    public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (;;) {
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                if (nanos <= 0)
                    return false;
                // 在终止条件termination上阻塞等待指定时长,有两种被唤醒的条件
                // 一是超时时间到了,二是被termination.signalAll()唤醒;唤醒之后便可以继续执行后续逻辑    
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }

通常当我们使用 shutdown() 方法关闭线程池时,虽然是比较友好的告诉系统此线程池正在关闭,但不知道明确的关闭时间(取决于任务的数量和任务的处理时长); 这个时候我们可以使用 awaitTermination 方法指定一个超时时间,在termination 条件上等待,当超时时间到了或者提前被唤醒(比如termination.signalAll()),就可以执行后面的逻辑了

5.3 shutdownNow

停止所有正在执行的线程,清空队列中等待执行的task

   // 返回队列中未执行的task列表
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            // 将线程池的状态设置为STOP
            advanceRunState(STOP);
            // 中断所有worker
            interruptWorkers();
            // 清空队列
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        
        // 尝试终止线程池
        // 虽然线程池状态已经改成了STOP状态,但还需要workers被清空之后才会真正变成TERMINATED状态
        // 所以这里不一定会成功, runWorker方法中处理worker退出时会触发tryTerminate
        tryTerminate();
        return tasks;
    }
    
    private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }
    
        // Worker内部方法
        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }        

三、小结

线程池“线程复用”原理:

runWorker(Worker w) {
    Runnable task = w.firstTask;
    while (task != null || (task = getTask()) != null) {
        try {
            task.run();
        } finally {
            task = null;
        }
    }
}

可以看出,实现线程复用的逻辑主要在一个不停循环的 while 循环体中

  • 通过取 Worker 的 firstTask 或者通过 getTask 方法从 workQueue 中获取待执行的任务。
  • 直接调用 task 的 run 方法来执行具体的任务(而不是新建线程)

合适的线程数量是多少?CPU 核心数和线程数的关系?

  • CPU 密集型任务,比如加密、解密、压缩、计算等一系列需要大量耗费 CPU 资源的任务。对于这样的任务最佳的线程数为 CPU 核心数的 1~2 倍,如果设置过多的线程数,实际上并不会起到很好的效果。此时假设我们设置的线程数量是 CPU 核心数的 2 倍以上,因为计算任务非常重,会占用大量的 CPU 资源,所以这时 CPU 的每个核心工作基本都是满负荷的,而我们又设置了过多的线程,每个线程都想去利用 CPU 资源来执行自己的任务,这就会造成不必要的上下文切换,此时线程数的增多并没有让性能提升,反而由于线程数量过多会导致性能下降
  • IO密集型,比如数据库、文件的读写,网络通信等任务,这种任务的特点是并不会特别消耗 CPU 资源,但是 IO 操作很耗时,总体会占用比较多的时间。对于这种任务最大线程数一般会大于 CPU 核心数很多倍,因为 IO 读写速度相比于 CPU 的速度而言是比较慢的,如果我们设置过少的线程数,就可能导致 CPU 资源的浪费。而如果我们设置更多的线程数,那么当一部分线程正在等待 IO 的时候,它们此时并不需要 CPU 来计算,那么另外的线程便可以利用 CPU 去执行其他的任务,互不影响,这样的话在任务队列中等待的任务就会减少,可以更好地利用资源

《Java并发编程实战》的作者 Brain Goetz 推荐的计算方法

线程数 = CPU 核心数 *(1+平均等待时间/平均工作时间)

如何正确关闭线程池?shutdown 和 shutdownNow 的区别?

  • shutdown(),它可以安全地关闭一个线程池,调用 shutdown方法之后线程池并不是立刻就被关闭,因为这时线程池中可能还有很多任务正在被执行,或是任务队列中有大量正在等待被执行的任务;新提交的任务会被直接拒绝

  • awaitTermination(),主要用来判断线程池状态的。比如我们给 awaitTermination 方法传入的参数是 10 秒,那么它就会陷入 10 秒钟的等待,直到发生以下三种情况之一

    • 等待期间(包括进入等待状态之前)线程池已关闭并且所有已提交的任务(包括正在执行的和队列中等待的)都执行完毕,相当于线程池已经“终结”了,方法便会返回 true;
    • 等待超时时间到后,第一种线程池“终结”的情况始终未发生,方法返回 false;
    • 等待期间线程被中断,方法会抛出 InterruptedException 异常
  • shutdownNow(),表示立刻关闭的意思,在执行 shutdownNow 方法之后,首先会给所有线程池中的线程发送 interrupt 中断信号,尝试中断这些任务的执行,然后会将任务队列中正在等待的所有任务清空并将未执行的任务返回,我们可以根据返回的任务 List 来进行一些补救的操作