线程池中关键的ThreadPoolExecutor类原理分析

java.uitl.concurrent.ThreadPoolExecutor 类是 Executor 框架中最核心的类。

线程池简介

什么是线程池

线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务。

为什么要使用线程池

因为 Java 中创建一个线程,需要调用操作系统内核的 API,操作系统要为线程分配一系列的资源,成本很高,所以线程是一个重量级的对象,应该避免频繁创建和销毁。

使用线程池就能很好地避免频繁创建和销毁,所以有必要引入线程池。使用 线程池的好处 有以下几点:

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌。

线程池状态和工作线程数量

这本来是两个不同的概念,但是在ThreadPoolExecutor中我们使用一个变量ctl来存储线程池状态和工作线程数量,这样我们只需要维护这一个变量的并发问题,提高运行效率。

/**
 * 记录线程池中Worker工作线程数量和线程池的状态
 * int类型是32位,它的高3位,表示线程池的状态,低29位表示Worker的数量
 */
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// COUNT_BITS 29位,
private static final int COUNT_BITS = Integer.SIZE - 3;
// 表示线程池中创建Worker工作线程数量的最大值。即 0b0001.....1(29位1)
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

怎么使用一个变量ctl存储两个值呢?

就是利用int变量的高3位来储存线程池状态,用int变量的低29位来储存工作线程数量。

这样就有两个需要注意的地方:

  • 工作线程数量最大值不能超过int类型29位的值CAPACITY 即0b0001.....1(29位1)
  • 因为线程池状态都是高3位储存的,所以工作线程数量不会影响状态值大小关系。

线程池状态

// 高3位值是111
private static final int RUNNING    = -1 << COUNT_BITS;
// 高3位值是000
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 高3位值是001
private static final int STOP       =  1 << COUNT_BITS;
// 高3位值是010
private static final int TIDYING    =  2 << COUNT_BITS;
// 高3位值是011
private static final int TERMINATED =  3 << COUNT_BITS;

线程池状态分析:

① RUNNING:运行状态。接受新任务,并且也能处理阻塞队列中的任务。

② SHUTDOWN:关闭状态。不接受新任务,但可以处理阻塞队列中的任务。

  • 在线程池处于 RUNNING 状态时,调用 shutdown 方法会使线程池进入到该状态。
  • finalize 方法在执行过程中也会调用 shutdown 方法进入该状态。

③ STOP:停止状态。不接受新任务,也不处理队列中的任务。会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow 方法会使线程池进入到该状态。

④ TIDYING:整理状态。如果所有的任务都已终止了,workerCount (有效线程数) 为 0,线程池进入该状态后会调用 terminated 方法进入 TERMINATED 状态。

⑤ TERMINATED:已终止状态。在 terminated 方法执行完后进入该状态。默认 terminated 方法中什么也没有做。进入 TERMINATED 的条件如下:

  • 线程池不是 RUNNING 状态;
  • 线程池状态不是 TIDYING 状态或 TERMINATED 状态;
  • 如果线程池状态是 SHUTDOWN 并且 workerQueue 为空;
  • workerCount 为 0;
  • 设置 TIDYING 状态成功。

操作ctl的方法

获取线程池的状态

/**
 * 获取线程池的状态。因为线程池的状态是使用高3位储存,所以屏蔽低29位就行了。
 * 所以就c与~CAPACITY(0b1110..0)进行&操作,屏蔽低29位的值了。
 * 注意:这里是屏蔽低29位的值,而不是右移29位。
 */
private static int runStateOf(int c)     { return c & ~CAPACITY; }

获取工作线程数量

/**
 * 获取线程池中Worker工作线程的数量,
 * 因为只使用低29位保存Worker的数量,只要屏蔽高3位的值就行了
 * 所以就c与CAPACITY(0b0001...1)进行&操作,屏蔽高3位的值了。
 */
private static int workerCountOf(int c)  { return c & CAPACITY; }

合并ctl的值

/**
 * 得到ctl的值。
 * 接受两个参数rs和wc。rs表示线程池的状态,wc表示Worker工作线程的数量。
 * 对于rs来说我们只需要高3位的值,对于wc来说我们需要低29位的值。
 * 所以我们将rs | wc就可以得到ctl的值了。
 */
private static int ctlOf(int rs, int wc) { return rs | wc; }

其他方法

// 因为RUNNING状态高三位是111,所以状态值rs与工作线程数量ws相与的结果值c一定是个负数,
// 而其他状态值都是大于等于0的数,所以c是负数,那么表示当前线程处于运行状态。
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}

/**
 * 使用CAS函数将ctl值自增
 */
private boolean compareAndIncrementWorkerCount(int expect) {
    return ctl.compareAndSet(expect, expect + 1);
}

/**
 * 使用CAS函数将ctl值自减
 */
private boolean compareAndDecrementWorkerCount(int expect) {
    return ctl.compareAndSet(expect, expect - 1);
}
/**
 * 使用CAS函数加循环方法这种乐观锁的方式,解决并发问题。
 * 保证使ctl值减一
 */
private void decrementWorkerCount() {
    do {} while (! compareAndDecrementWorkerCount(ctl.get()));
}

重要成员变量

// 记录线程池中Worker工作线程数量和线程池的状态
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

// 任务线程的阻塞队列,因为是阻塞队列,所以它是并发安全的
private final BlockingQueue<Runnable> workQueue;

// 独占锁,用来保证操作成员变量的并发安全问题
private final ReentrantLock mainLock = new ReentrantLock();

// 等待线程池完全终止的条件Condition,
private final Condition termination = mainLock.newCondition();

//-----------------  需要mainLock来保证并发安全-------------------------//
// 线程池中工作线程集合。Worker中持有线程thread变量
private final HashSet<Worker> workers = new HashSet<Worker>();

// 线程池中曾拥有过的最大工作线程个数
private int largestPoolSize;

// 线程池完成过任务的总个数
private long completedTaskCount;
//-----------------  需要mainLock来保证并发安全-------------------------//

// 创建线程的工厂类
private volatile ThreadFactory threadFactory;

// 当任务被拒绝时,用来处理这个被拒绝的任务
private volatile RejectedExecutionHandler handler;
// 工作线程空闲的超时时间keepAliveTime
private volatile long keepAliveTime;

// 是否允许核心池线程超时释放
private volatile boolean allowCoreThreadTimeOut;

// 线程池核心池线程个数
private volatile int corePoolSize;

// 线程池最大的线程个数
private volatile int maximumPoolSize;

成员变量说明:

  • mainLock:使用mainLock来保证会发生变化成员变量的并发安全问题。会发生的成员变量有5个:ctl、workQueue、workers、largestPoolSize和completedTaskCount。但是其中ctl和workQueue的类型本身就是多线程安全的,所以不用mainLock锁保护。
  • termination:等待线程池完全终止的条件,如果线程池没有完全终止,调用它的awaitNanos方法,让线程等待。当线程池完全终止后,调用它的signalAll方法,唤醒所有等待termination条件的线程。
  • workers:记录所有的工作线程Worker
  • workQueue:记录所有待执行的任务。使用阻塞队列BlockingQueue,可以在队列为空时,线程等待,队列有值时,唤醒等待的线程。
  • largestPoolSize:线程池中曾拥有过的最大工作线程个数
  • completedTaskCount:线程池完成过任务的总个数
  • threadFactory:创建线程的工厂类
  • handler:当任务被拒绝时,用来处理这个被拒绝的任务
  • keepAliveTime:工作线程允许空闲的超时时间,一般都是针对超过核心池数量的工作线程。
  • allowCoreThreadTimeOut: 是否允许核心池的工作线程超时释放。
  • corePoolSize:线程池核心池线程个数。
  • maximumPoolSize: 线程池最大的线程个数。

构造方法

ThreadPoolExecutor 有四个构造方法,前三个都是基于第四个实现。第四个构造方法定义如下:

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

参数说明:

1、corePoolSize:核心线程数量。当有新任务通过 execute 方法提交时 ,线程池会执行以下判断:

  • 如果运行的线程数少于 corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的;
  • 如果线程池中的线程数量大于等于 corePoolSize 且小于 maximumPoolSize,则只有当 workQueue 满时才创建新的线程去处理任务;
  • 如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建的线程池的大小是固定的。这时如果有新任务提交,若 workQueue 未满,则将请求放入 workQueue 中,等待有空闲的线程去从 workQueue 中取任务并处理;
  • 如果运行的线程数量大于等于 maximumPoolSize,这时如果 workQueue 已经满了,则使用 handler 所指定的策略来处理任务;

所以,任务提交时,判断的顺序为 corePoolSize => workQueue => maximumPoolSize。

2、maximumPoolSize:最大线程数量

  • 如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。

值得注意的是:如果使用了无界的任务队列这个参数就没什么效果。

3、keepAliveTime:线程保持活动的时间(非核心线程的存活时间)

当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime。

所以,如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

4、unit:keepAliveTime 的时间单位。有 7 种取值。可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

5、workQueue - 等待执行的任务队列。用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。

(1)ArrayBlockingQueue - 有界阻塞队列。

此队列是基于数组的先进先出队列(FIFO)。

此队列创建时必须指定大小。

(2)LinkedBlockingQueue - 无界阻塞队列。

此队列是基于链表的先进先出队列(FIFO)。
如果创建时没有指定此队列大小,则默认为 Integer.MAX_VALUE。
吞吐量通常要高于 ArrayBlockingQueue。
使用 LinkedBlockingQueue 意味着: maximumPoolSize 将不起作用,线程池能创建的最大线程数为 corePoolSize,因为任务等待队列是无界队列。
Executors.newFixedThreadPool 使用了这个队列。

(3)SynchronousQueue - 不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。
吞吐量通常要高于 LinkedBlockingQueue。
Executors.newCachedThreadPool 使用了这个队列。

(4)PriorityBlockingQueue - 具有优先级的无界阻塞队列。

6、threadFactory - 线程工厂。可以通过线程工厂给每个创建出来的线程设置更有意义的名字。

7、handler - 饱和策略。它是 RejectedExecutionHandler 类型的变量。当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。线程池支持以下策略:

AbortPolicy - 丢弃任务并抛出异常。这也是默认策略。
DiscardPolicy - 丢弃任务,但不抛出异常。
DiscardOldestPolicy - 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。
CallerRunsPolicy - 只用调用者所在的线程来运行任务。

如果以上策略都不能满足需要,也可以通过实现 RejectedExecutionHandler 接口来定制处理策略。如记录日志或持久化不能处理的任务。

常用方法

执行任务execute方法

在线程池中如何执行一个任务command,要分三种情况:

  • 线程池中工作线程的数量没有达到核心池个数,那么线程池就应该开启新的工作线程来执行任务。
  • 线程池中工作线程的数量达到核心池个数,那么就应该将任务添加到任务队列中,等待着工作线程去任务队列中获取任务并执行。
  • 如果任务添加到任务队列失败,那么就要开启新的工作线程来执行任务。
public void execute(Runnable command) {
    // 如果command为null,抛出异常
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 分为三个步骤:
     * 1. 如果运行的工作线程数量少于核心池数量corePoolSize,
     * 那么就调用addWorker方法开启一个新的工作线程,运行任务command。
     * 2. 如果开启新的工作线程失败,就将任务添加到任务队列中。
     * 3. 添加到任务队列失败,
     * 那么仍然addWorker方法在最大池中开启一个新的工作线程,运行任务command。     
     */
    int c = ctl.get();
    // 运行的工作线程数量少于核心池数量corePoolSize
    if (workerCountOf(c) < corePoolSize) {
        /**
         * 开启一个新的工作线程,运行任务command。
         * 返回true,表示开启工作线程成功,直接return。
         * 返回false,表示没有开启新线程。那么任务command就没有运行,所以要执行下面代码。
          */
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 线程池处于运行状态,
    // 且任务添加到任务阻塞队列workQueue中成功,即workQueue队列有剩余空间。
    if (isRunning(c) && workQueue.offer(command)) {
        // 再次检查线程池状态和工作线程数量
        int recheck = ctl.get();
        /**
         * 如果线程池不在运行状态,那么就调用remove方法移除workQueue队列这个任务command,
         * 如果移除成功,那么调用reject(command)方法,进行拒绝任务的处理。
         * 如果移除失败,那么这个任务还是会被执行,那么就不用调用reject(command)方法
         */
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 如果工作线程数量为0,但是workQueue队列中我们添加过任务,
        // 那么必须调用addWorker方法,开启一个新的工作线程。
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 调用addWorker方法,开启一个新的工作线程,运行任务command。
    // 如果还是失败,那么这个任务command就不会不可能执行了,
    // 那么调用reject(command)方法拒绝这个任务
    else if (!addWorker(command, false))
        reject(command);
}

方法流程上面已经有标注,注意有以下几点:

  • addWorker(Runnable firstTask, boolean core):表示开启一个新的工作线程执行任务firstTask。core是用来判断核心池还是最大池。返回false,表示开启新线程失败,即任务firstTask没有机会执行。
  • isRunning(c)线程池处于RUNNING状态,只有处于RUNNING状态下,才能将任务添加到任务队列。
  • reject(command) 当任务command不能在线程池中执行时,就会调用这个方法,告诉调用值,线程池拒绝执行这个任务。

添加工作线程addWorker方法

private boolean addWorker(Runnable firstTask, boolean core) {
    // 利用死循环和CAS函数,实现乐观锁,来实现多线程改变ctl值的并发问题
    // 因为ctl值代表两个东西,工作线程数量和线程池状态。
    // 这里就用了两个for循环,一个是线程池状态的for循环,一个是工作线程数量的for循环
    retry:
    for (;;) {
        int c = ctl.get();
        // 获取线程池运行状态rs,
        int rs = runStateOf(c);

        // 首先判断线程池状态和任务队列状态,
        // 来判断能否创建新的工作线程
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
        
        for (;;) {
            // 线程池中工作线程数量wc
            int wc = workerCountOf(c);
            // 当线程池工作线程数量wc大于线程上限CAPACITY,
            // 或者用户规定核心池数量corePoolSize或用户规定最大线程池数量maximumPoolSize
            // 表示不能创建工作线程了,所以返回false
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 使用CAS函数,使工作线程数量wc加一
            if (compareAndIncrementWorkerCount(c))
                // 跳出retry循环
                break retry;
            // 来到这里表示CAS函数失败,那么就要循环重新判断
            // 但是c还代表线程状态,如果线程状态改变,那么就必须跳转到retry循环
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    // 工作线程是否开始,即调用了线程的start方法
    boolean workerStarted = false;
    // 工作线程是否添加到工作线程队列workers中
    boolean workerAdded = false;
    Worker w = null;
    try {
        // 创建一个Worker对象
        w = new Worker(firstTask);
        // 得到Worker所拥有的线程thread
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            // 并发锁
            mainLock.lock();
            try {
                //  获取线程池运行状态rs
                int rs = runStateOf(ctl.get());

                // 当线程池是运行状态,或者是SHUTDOWN状态但firstTask为null,
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    // 如果线程t已经被开启,就抛出异常
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // 将w添加到工作线程集合workers中
                    workers.add(w);
                    // 获取工作线程集合workers的个数
                    int s = workers.size();
                    // 记录线程池历史最大的工作线程个数
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            // 如果已经添加到工作线程队列中,那么开启线程
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        // 如果开启工作线程失败,那么这个任务也就没有执行
        // 因此移除这个任务w(如果队列中有),减少工作线程数量,因为这个数量在之前已经增加了
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

添加一个新的工作线程,就涉及到两个成员变量的改变,一个是工作线程数量ctl,一个是工作线程集合workers。而ctl的类型是AtomicInteger,所以它可以使用乐观锁解决并发问题,workers就只能使用mainLock互斥锁来保证并发安全问题。

更改工作线程数量ctl

因为ctl储存了两个值,工作线程数量和线程池状态。所以使用了两个for循环来监控多线程对这两个值的更改。

用线程池状态来判断是否允许添加新的工作线程:

//  是对addWorker中线程状态if判断的拆分
// 当线程池不是处于运行状态
if (rs >= SHUTDOWN) {
    /**
     * 线程池状态不是SHUTDOWN,或者firstTask不为null,或者任务队列为空,
     * 都直接返回false,表示开启新工作线程失败。
     * 只有当线程池状态是SHUTDOWN,firstTask为null,任务队列不为空时,
     * 需要创建新的工作线程。
     * 从execute(Runnable command)方法中分析,firstTask参数为空只有一种情况,
     * 此时线程池中工作线程数量是0,而任务队列不为空,
     * 那么就要开启一个新工作线程去执行任务队列中的任务,否则这些任务会被丢失。
     */
    if (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty()) {
        return false;
    }
}

由此可以得出,只有两种情形允许添加新的工作线程:

  • 线程池处于RUNNING状态
  • 线程池虽然处于SHUTDOWN状态,但是线程池工作线程个数是0(即这里的firstTask != null),且任务队列workQueue不为空,那么就要开启一个新工作线程去执行任务队列中的任务。

然后使用for循环和CAS函数方式,来给工作线程数量加一。注意此时工作线程还没有创建,并添加到线程集合workers中,所以如果线程添加失败,那么还要将工作线程数量减一。

添加工作线程集合workers

创建一个工作线程Worker,将它添加到线程集合workers中,然后开启这个工作线程,使用mainLock独占锁保证成员变量workers的并发安全问题。

内部类Worker

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    private static final long serialVersionUID = 6138294804551838833L;

    /** 该Worker所拥有的工作线程 */
    final Thread thread;
    /** Worker拥有的第一个任务,初始化的时候赋值 */
    Runnable firstTask;
    /** 该工作线程Worker完成任务的数量 */
    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,禁止发起中断请求,
        // 直到调用过runWorker方法,即线程已经运行时。
        setState(-1);
        // 第一个任务
        this.firstTask = firstTask;
        // 创建一个thread线程对象,它的run方法就是本Worker的run方法
        // 这个thread就是Worker真正执行任务的工作线程
        this.thread = getThreadFactory().newThread(this);
    }

    /** 复写的是Runnable中的run方法,所以当工作线程开启运行后,会调用这个方法。  */
    public void run() {
        runWorker(this);
    }

    // 当前独占锁是否空闲
    protected boolean isHeldExclusively() {
        return getState() != 0;
    }

    // 尝试获取独占锁
    protected boolean tryAcquire(int unused) {
        // 如果通过CAS函数,可以将state值从0改变成1,那么表示获取独占锁成功。
        // 否则独占锁被别的线程获取了。
        if (compareAndSetState(0, 1)) {
            // 设置拥有独占锁的线程是当前线程
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    // 释放独占锁
    protected boolean tryRelease(int unused) {
        // 设置拥有独占锁的线程为null
        setExclusiveOwnerThread(null);
        // 设置获取独占锁的次数是0,表示锁是空闲状态
        setState(0);
        return true;
    }

    // 获取独占锁,如果锁被别的获取,就一直等待。
    public void lock()        { acquire(1); }
    // 尝试获取独占锁,如果锁被别的获取,就直接返回false,表示获取失败。
    public boolean tryLock()  { return tryAcquire(1); }
    // 释放独占锁
    public void unlock()      { release(1); }
    // 当前独占锁是否空闲
    public boolean isLocked() { return isHeldExclusively(); }

    // 如果Worker的工作线程thread已经开启,那么发起中断请求。
    void interruptIfStarted() {
        Thread t;
        // getState() >= 0表示thread已经开启
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

Worker实现了Runnable接口,那么就可以通过Worker对象创建一个新线程thread,这个thread就是Worker的工作线程,而任务都在run方法中执行。

Worker还继承自AbstractQueuedSynchronizer类。我们知道可以通过AQS类实现独占锁和共享锁,而Worker中实现了tryAcquire和tryRelease方法,说明Worker对象也是个独占锁对象。我们可以考虑一下Worker这个独占锁的作用是什么?

工作线程运行任务runWorker方法

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    // 将w的state状态设置成0,这样就允许对w的thread线程进行中断请求了。
    w.unlock();
    // completedAbruptly表示线程突然终结
    boolean completedAbruptly = true;
    try {
        // 通过getTask从任务队列中获取任务task执行,这个方法是个阻塞方法。
        while (task != null || (task = getTask()) != null) {
            // 获取w独占锁,保证当本工作线程运行任务时,
            // 不能对该线程进行中断请求。
            w.lock();
            /**
             * 如果线程池大于STOP状态,且Worker工作线程中断标志位是false,
             * 那么就调用wt的interrupt方法发起中断请求。
             */
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                // Worker工作线程发起中断请求
                wt.interrupt();
            try {
                // 钩子方法,提供给子类。在执行任务之前调用
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    // 调用run方法,执行任务
                    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,进行下一次循环
                task = null;
                // 将work完成的任务数completedTasks加一
                w.completedTasks++;
                // 释放w独占锁
                w.unlock();
            }
        }
        // completedAbruptly = false表示线程正常完成终结
        completedAbruptly = false;
    } finally {
        // 进行一个工作线程完结后的后续操作
        processWorkerExit(w, completedAbruptly);
    }
}

runWorker方法是在每个工作线程的run方法中调用,通过getTask()方法从任务队列中获取任务task执行,这个方法可以阻塞当前工作线程,如果getTask()方法返回null,那么工作线程就会运行结束,释放线程。

虽然runWorker方法运行在每个工作线程中,但是对于一个Worker来说,只要它的工作线程能够运行runWorker方法,而且改变的也是这个Worker的成员变量,且这些成员变量也只能在runWorker方法改变,那么它就没有多线程并发问题啊,那么为什么在这里加锁呢?
这是因为Worker中有一个变量是可以被其他线程改变的,就是它的工作线程thread的中断请求,所以Worker独占锁的作用就是控制别的线程对它的工作线程thread中断请求的。

最后调用processWorkerExit方法,进行一个工作线程完结后的后续操作。

获取任务getTask方法

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        // 获取线程池状态rs
        int rs = runStateOf(c);

        // 如果有需要检查任务队列workQueue是否为空
        // 即rs >= STOP或者rs == SHUTDOWN且workQueue为空,那么返回null,停止工作线程
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            // 将工作线程数量减一
            decrementWorkerCount();
            return null;
        }

        // 获取工作线程数量wc
        int wc = workerCountOf(c);

        /**
         * 如果allowCoreThreadTimeOut为true或者wc > corePoolSize时,
         * 就要减少工作线程数量了。
         * 当工作线程在keepAliveTime时间内,没有获取到可执行的任务,
         * 那么该工作线程就要被销毁。
         */
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            // 工作线程数量减一,返回null,销毁工作线程。
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 从任务队列workQueue中获取了任务r,会阻塞当前线程。
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            // 如果r不为null,返回这个任务r
            if (r != null)
                return r;
            // r是null,表示获取任务超时
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

从阻塞任务队列workQueue中获取任务返回,因为是阻塞任务队列,所以可以阻塞当前线程。如果返回null,那么会完结调用getTask方法的那个工作线程。那么getTask方法在什么情况下返回null呢?

  • 线程池的状态大于等于STOP,或者线程状态是SHUTDOWN且当前任务队列为空,那么返回null,停止工作线程。
  • 获取任务时间超时,那么也会返回null,停止工作线程。因为线程池一般只维护一定数量的工作线程,如果超过这个数量,那么超过数量的工作线程,在空闲一定时间后,应该被释放。

终止线程池的方法

shutdown和shutdownNow方法

/**
 * 终止线程池。不能在添加新任务了,但是已经添加到任务队列的任务还是会执行。
 * 且对所有不是正在执行任务的工作线程都发起中断请求
 */
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 检查是否拥有Shutdown的权限
        checkShutdownAccess();
        // 将线程池状态变成SHUTDOWN状态
        advanceRunState(SHUTDOWN);
        // 对所有不是正在执行任务的工作线程都发起中断请求
        interruptIdleWorkers();
        // 钩子方法,提供给子类实现。表示线程池已经shutdown了
        onShutdown();
    } finally {
        mainLock.unlock();
    }
    // 尝试去终结线程池
    tryTerminate();
}

/**
 * 终止线程池。不能在添加新任务了,也不会执行已经添加到任务队列的任务,只是将这些任务返回。
 * 且对所有工作线程都发起中断请求, 不管这个工作线程是否正在执行任务
 */
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 检查是否拥有Shutdown的权限
        checkShutdownAccess();
        // 将线程池状态变成STOP状态
        advanceRunState(STOP);
        // 对所有工作线程都发起中断请求, 不管这个工作线程是否正在执行任务
        interruptWorkers();
        // 返回阻塞队列workQueue中未执行任务的集合
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    // 尝试去终结线程池
    tryTerminate();
    return tasks;
}

shutdown和shutdownNow区别:

  • shutdown方法将线程池设置成SHUTDOWN状态,shutdownNow将线程池设置成STOP状态。
  • shutdown方法调用之后不能再添加新任务了,但是已经添加到任务队列的任务还是会执行。shutdownNow方法调用之后不能再添加新任务了,也不会执行已经添加到任务队列的任务,只是将这些任务返回。
  • shutdown方法会对所有不是正在执行任务的工作线程都发起中断请求,shutdownNow方法会对所有工作线程都发起中断请求, 不管这个工作线程是否正在执行任务。

advanceRunState方法

private void advanceRunState(int targetState) {
    // 采用乐观锁的方法,来并发更改线程池状态。
    for (;;) {
        int c = ctl.get();
        // 如果runStateAtLeast方法返回true,表示当前线程池状态已经是目标状态targetState
        // 采用CAS函数尝试更改线程池状态,如果失败就循环继续。
        if (runStateAtLeast(c, targetState) ||
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break;
    }
}

这个方法来改变线程池状态,使用乐观锁的方式保证并发安全。

中断空闲状态下的工作线程

/**
 * 对所有不是正在执行任务的工作线程都发起中断请求。
 */
private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 遍历工作线程Worker集合
        for (Worker w : workers) {
            Thread t = w.thread;
            // 如果工作线程中断标志位是false,
            // 且能够获取锁,即当前工作线程没有运行任务
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    // 发起中断请求。
                    // 因为获取了锁,所以在进入中断请求时,worker工作线程不会执行任务
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    // 释放锁
                    w.unlock();
                }
            }
            // 是否只进行一个工作线程的中断请求。
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

遍历工作线程Worker集合,如果工作线程处于空闲状态,且没有被中断,那么就发起中断请求。通过独占锁Worker知道,当前工作线程是否在执行任务。

对所有已开启的工作线程发起中断请求

/**
 * 对所有工作线程都发起中断请求, 不管这个工作线程是否正在执行任务
 */
private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
             // 如果w的工作线程thread已经开启,那么发起中断请求。
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

遍历工作线程Worker集合,调用Worker的interruptIfStarted方法,如果工作线程已开启,那么就会发起中断。

尝试完结线程池的方法

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        /**
         * 如果线程池是RUNNING状态,
         * 或者线程池是TIDYING状态(是因为已经有别的线程在终止线程池了)
         * 或者线程池是SHUTDOWN状态且任务队列不为空,
         * 线程池不能被terminate终止,直接return返回
         *
         */
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        // 线程池中工作线程数量不是0,线程池不能被terminate终止,所以要return
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    // 钩子方法,提供给子类实现。表示线程池已经终止。
                    terminated();
                } finally {
                    // 设置线程池状态是TERMINATED
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
    }
}

线程池在什么情况下算是完全停止了呢?有三个条件:

  1. 线程池不是RUNNING状态。
  2. 线程池中工作线程数量是0。
  3. 线程池中任务队列为空。

所以再看看tryTerminate(),前面两个if判断条件,就可以理解了。

等待线程池完结的方法

public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (;;) {
            // 如果是TERMINATED已终止状态,那么就返回true
            if (runStateAtLeast(ctl.get(), TERMINATED))
                return true;
            // 如果已经超时就返回false
            if (nanos <= 0)
                return false;
            // 让当前线程等待。并设置超时时间nanos
            nanos = termination.awaitNanos(nanos);
        }
    } finally {
        mainLock.unlock();
    }
}

如果线程池不是TERMINATED状态,就让当前线程在termination条件上等待,直到线程池变成TERMINATED状态,或者等待时间超时才会被唤醒。

工作线程退出的方法

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // 如果工作线程突然被终结,那么工作线程的数量就没有减一。
    if (completedAbruptly)
        // 将工作线程数量减一。
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 将工作线程的任务完成数添加到线程池完成任务总数中
        completedTaskCount += w.completedTasks;
        // 从工作线程集合中移除本工作线程
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    // 因为有一个工作线程已经完成被释放,那么就去尝试终结线程池。
    tryTerminate();

    int c = ctl.get();
    // 如果线程池状态小于STOP,
    // 就要判断终结了这个工作线程之后,线程池中工作线程数量是否满足需求。
    if (runStateLessThan(c, STOP)) {
        // 如果工作线程正常终结,
        // 那么要看线程池中工作线程数量是否满足需求。
        if (!completedAbruptly) {
            // 不允许核心池线程释放,那么最小值是corePoolSize,否则就可以为0
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            // 但是如果任务队列中还有任务,那么工作线程数量最少为1
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            // 如果工作线程数量小于min值,就要创建新的工作线程了。
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        // 开启一个新的工作线程
        addWorker(null, false);
    }
}

工作线程被释放,有两种情况,一种是运行完成正常结束,一种是发生异常意外终止。

当工作线程被释放时,需要将它从工作线程集合workers中移除,将该工作线程任务完成数添加到线程池完成任务总数中。调用tryTerminate方法尝试终结线程池。

另外因为有一个工作线程被释放,那么就要考虑线程池中当前工作线程数量是否符合要求,要不要添加新的工作线程。

线程工厂ThreadFactory

设置线程名称是很重要的,尤其是在排查问题的时候。

在JDK中,有实现ThreadFactory就只有一个地方(DefaultThreadFactory)。而更多的时候,我们都是继承它然后自己来写这个线程工厂的

static class DefaultThreadFactory implements ThreadFactory {  
    private static final AtomicInteger poolNumber = new AtomicInteger(1);//原子类,线程池编号  
    private final ThreadGroup group;//线程组  
    private final AtomicInteger threadNumber = new AtomicInteger(1);//线程数目  
    private final String namePrefix;//为每个创建的线程添加的前缀  
  
    DefaultThreadFactory() {  
        SecurityManager s = System.getSecurityManager();  
        group = (s != null) ? s.getThreadGroup() :  
                              Thread.currentThread().getThreadGroup();//取得线程组  
        namePrefix = "pool-" +  
                      poolNumber.getAndIncrement() +  
                     "-thread-";  
    }  
  
    public Thread newThread(Runnable r) {  
        Thread t = new Thread(group, r,  
                              namePrefix + threadNumber.getAndIncrement(),  
                              0);//真正创建线程的地方,设置了线程的线程组及线程名  
        if (t.isDaemon())  
            t.setDaemon(false);  
        if (t.getPriority() != Thread.NORM_PRIORITY)//默认是正常优先级  
            t.setPriority(Thread.NORM_PRIORITY);  
        return t;  
    }  
}

在上面的代码中,可以看到线程池中默认的线程工厂实现是很简单的,它做的事就是统一给线程池中的线程设置线程group、统一的线程前缀名以及统一的优先级。

第一种:Spring框架提供的CustomizableThreadFactory

ThreadFactory springThreadFactory = new CustomizableThreadFactory("springThread-pool-");
ExecutorService exec = new ThreadPoolExecutor(1, 1,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(10), springThreadFactory);
exec.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":记忆过了.....");
    }
});

说明:Spring框架也提供了线程池类ThreadPoolTaskExecutor

ThreadPoolTaskExecutor threadExecutor = new ThreadPoolTaskExecutor();
threadExecutor.setCorePoolSize(CORE_POOL_SIZE);
threadExecutor.setMaxPoolSize(MAX_POOL_SIZE);

threadExecutor.setQueueCapacity(5000);
threadExecutor.setKeepAliveSeconds(KEEP_ALIVE_TIME);
threadExecutor.setThreadNamePrefix("elasticsearch-task");
threadExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
threadExecutor.initialize();

第二种:Google guava 工具类 提供的 ThreadFactoryBuilder ,使用链式方法创建。

//这里需要添加%d, 在源码中会与序号进行拼接
ThreadFactory guavaThreadFactory = new ThreadFactoryBuilder().setNameFormat("guava-pool-%d").build();
ExecutorService exec = new ThreadPoolExecutor(1, 1,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(10), guavaThreadFactory);
exec.submit(() -> {
    System.out.println(Thread.currentThread().getName() + ":--记忆中的颜色是什么颜色---");
});

第三种:Apache commons-lang3 提供的 BasicThreadFactory。

//这里需要添加%d,在源码中会跟序号拼接
ThreadFactory basicThreadFactory = new BasicThreadFactory.Builder()
        .namingPattern("basicThreadFactory-%d").build();
ExecutorService exec = new ThreadPoolExecutor(1, 1,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(10), basicThreadFactory);
exec.submit(() -> {
    System.out.println(Thread.currentThread().getName() + ":--记忆中的颜色是什么颜色---");
});

饱和策略(拒绝策略)

拒绝策略提供顶级接口 RejectedExecutionHandler ,其中方法 rejectedExecution 即定制具体的拒绝策略的执行逻辑。

jdk默认提供了四种拒绝策略

  • AbortPolicy - 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
  • CallerRunsPolicy - 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大。
  • DiscardPolicy - 直接丢弃,其他啥都没有。
  • DiscardOldestPolicy - 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入。

如果以上策略都不能满足需要,也可以通过实现 RejectedExecutionHandler 接口来定制处理策略。如记录日志或持久化不能处理的任务。

 

posted @ 2022-02-07 18:10  残城碎梦  阅读(109)  评论(0编辑  收藏  举报