Java 线程池详解

107 阅读14分钟

Java 线程池详解

概述

Java 线程池 详解,主要是 基于 ThreadPoolExecutor 出发。

宏观上会详细解释 该类中,每一个组件的作用,以及工作流程,微观会包括线程池的 创建、运行、任务提交 以及生命周期的管理等。

各组件介绍

内部类

  1. Worker:负责管理线程执行任务时的中断控制状态,确保工作线程正在等待执行任务时,不会被意外中断。
  2. 拒绝策略
    1. CallerRunsPolicy:调用线程执行任务
    2. AbortPolicy:拒绝任务,抛出异常
    3. DiscardPolicy:丢弃任务,空实现
    4. DiscardOldestPolicy:丢弃最旧的任务,然后 重新 提交任务

属性

  1. ctl:AtomicInteger,是整个 线程池的 核心控制 变量,作用见备注。
  2. mainLock:ReentrantLock。保证 工作线程的访问是线程安全的,使用锁,而不是 并发 集合的原因,是因为 也可以 基于 锁去 计算一些统计信息;串行化 对 interruptIdleWorkers 方法的调用,从而避免不必要的中断。
    1. 对工作线程集合的访问锁
      • 在线程池中,工作线程集合(workers set)是用来存放正在执行任务的工作线程的数据结构。为了保证对工作线程集合的访问是线程安全的,需要使用锁来进行同步。
      • 虽然可以使用某种并发集合(concurrent set),但通常更倾向于使用锁来管理工作线程集合的访问。其中一个原因是,这样可以串行化对**interruptIdleWorkers方法的调用,从而避免不必要的中断风暴,尤其是在线程池关闭过程中。否则,退出的线程可能会同时中断尚未中断的线程。另外,这种方式也简化了一些相关的统计信息的维护,例如largestPoolSize**等。
    2. 在关闭过程中持有主锁
      • 在执行线程池的关闭操作时,通常也需要持有主锁(mainLock)。这是为了确保在检查是否有权限中断线程和实际中断线程之间,工作线程集合保持稳定。
      • 在执行**shutdownshutdownNow**操作时,线程池会持有主锁。这样可以保证在分别检查是否有权限中断线程和实际中断线程之间,工作线程集合保持稳定。这种方式也有助于确保在线程池关闭过程中的正确性和可靠性。
  3. workers:HashSet,工作线程的集合。线程池所有的worker都在此处。访问次字段必须 拥有 mainLock。
  4. termination:Condition,对 awaitTermination 的支持
  5. container:工作线程的容器
  6. completedTaskCount:完成任务数
  7. allowCoreThreadTimeOut:默认是 false 。 如果为true ,则 核心线程也会 根据 keepAliveTime 在 空闲时 销毁。
  8. defaultHandler:默认的拒绝策略Handler:AbortPolicy
  9. shutdownPerm:在调用线程池的**shutdownshutdownNow**方法时,对调用者权限的要求,以及对工作线程集合进行中断操作时的安全性处理
  10. 线程池相关参数:
    1. largestPoolSize:记录线程池在运行过程中的最大并发线程数量
    2. maximumPoolSize:最大线程数
    3. workQueue:等待队列。加入 任务的时候,如果线程达到了最大核心 线程数,则会添加到此队列中
    4. threadFactory:线程工厂
    5. handler:拒绝策略Handler
    6. keepAliveTime:线程存活时间
    7. corePoolSize:核心线程数
💡 ctl 变量相关:CTL 是 一个原子整数,包括了 两个概念字段: - workerCount:表示有效的线程数量,即已经启动,但尚未停止的线程数量; - runState:表示线程池的运行状态。

CTL 是 一个 int 类型,占用 4个字节,32 为,其中 使用 29位 来存储 workerCount 的数量,所以能允许的 最大线程数 是 (2^29)-1(因为最高位是符号位,所以int 能表示的最大的 正数 是 (2^31)-1),使用了 29 位 来 表示 workerCount,剩下的 3 位 被看做 一个整体,用作 线程池状态控制。

runState 定义了线程池的生命周期,包括以下几个状态:

  1. RUNNING -1:接受新的 任务,并处理队列中的任务
  2. SHUTDOWN 0:不接受 新任务,但处理队列中的任务
  3. STOP 1:不接受新任务,不处理队列中的任务,并中断正在执行的任务
  4. TIDYING 2:所有任务已经终止,线程池正在转换状态,执行**terminated()**钩子方法。
  5. TERMINATED 3:线程池已经终止,不再接受新任务,不处理队列中的任务。 -1、0、1、2、3 可以 理解为他们的状态值 runState 状态转换:
  • RUNNING -> SHUTDOWN:调用**shutdown()**方法时。
  • RUNNINGSHUTDOWN -> STOP:调用**shutdownNow()**方法时。
  • SHUTDOWN -> TIDYING:当队列和线程池都为空时。
  • STOP -> TIDYING:当线程池为空时。
  • TIDYING -> TERMINATED:当**terminated()**钩子方法完成时。

内部详解

创建线程池

创建线程池 主要 有 7 个参数:

  1. corePoolSize:核心线程池数
  2. maximumPoolSize:最大线程池数
  3. keepAliveTime:存活时间
  4. unit:存活时间的 单位
  5. workQueue:等待队列
  6. threadFactory:线程工厂,默认使用:Executors.*defaultThreadFactory*()
  7. handler:拒绝策略

这每一个 参数的 作用 过程和作用方式,见 任务 提交分析。

任务提交

任务 提交分为了 两种:

  1. submit:提交任务,会返回 返回值
  2. execute:提交任务,没有返回值

submit方法

submit 方法 一共有三个 方法:

  1. Runnable:
    1. 返回值为 Null
    2. 参数新增 Result:返回值为 Result。
  2. Callable:返回值为 task 的返回值

Runnable 和 Callable 一样,Runnable 会在 内部 将 任务 封装成带 Future 返回值的任务。下面的额解析 就直接 以 Callable 参数为主。

提交任务的 过程主要有 2个 过程:

  1. 将 需要执行的 任务,进行封装,封装为 FutureTask 任务,并放回这个 Future 的 信息
  2. 执行 execute 方法,提交任务。该方法 即 后续的 execute 方法。此处 不展开。

execute方法

线程池提交任务的 核心方法。主要的过程如下:

  1. 通过 ctl ,判断 当前的 核心线程池数 是否 小于指定数量
    1. 小于 则 使用 addWorker 方法,core 参数为 true。
  2. 判断当前的 线程池状态是否是运行 状态
    1. 如果处于运行状态,则 添加到等待队列
      1. 如果 添加成功,会重新 检查运行状态
      2. 如果当前的工作线程数为 0 ,则会 调用 addWorker 添加一个空任务
  3. 如果线程池不处于运行状态,或者加入队列失败,则会重新尝试 调用 addWorker ,如果失败,则 拒绝任务
  public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        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);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

addWorker 承担了 大部分的逻辑:

addWorker

此方法的主要作用是 判断 线程池中,线程的数量,在需要的时候创建新的 线程,并开启线程。

  1. 进入循环,循环中判断 线程池状态
    1. 如果当先线程池 状态 大于等于 SHUTDOWN状态(TERMINATEDTIDYINGSTOPSHUTDOWN),并且 满足 一下三点之一,则返回 false。(SHUTDOWN 和后续状态不接受新任务)
      1. 当前线程池状态 为 TERMINATED 或者 TIDYING 或者 STOP
      2. 参数中的 task 信息不为空
      3. 等待队列为空
    2. 进入内循环
      1. 获取工作线程数,根据 core 参数判断,当前的 线程数 是否大于 核心线程数或者 最大线程数。 如果大于,则返回 false
      2. 增加 工作线程的 数量,更新 ctl 的值,退出外层循环
  2. 用当前的 任务 创建一个 新的 worker
    1. 设置当前worker 的状态
    2. 使用线程工厂创建一个线程
  3. 获取 mainLock
  4. 判断当前线程池 状态,如果 线程池 正在运行 或者 线程池 处于SHUTDOWN 或者 RUNNING 状态并且当前任务为空
    1. 将任务 添加到 workers 列表中
    2. 更新 largestPoolSize 的值
  5. 释放锁
  6. 如果 线程 已经成功添加到 workers 中,则使用 container 开启线程(调用 start 方法)
  7. 如果线程启动成功,则返回。如果线程启动失败,则尝试 终止线程池。
  private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (int c = ctl.get();;) {
            // Check if queue empty only if necessary.
            // 如果是 调用了 **shutdown 方法**
            if (runStateAtLeast(c, SHUTDOWN)
                && (runStateAtLeast(c, STOP) // **shutdownNow()**
                    || firstTask != null  任务不为空
                    || workQueue.isEmpty())) 等待队列为空
                return false;   // 表明当前线程 在 SHUTDOWN 状态,则不接受 任何 新的 任务

            for (;;) {
		            // 判断工作线程数 是否 达到了 预定 的线程数,
                if (workerCountOf(c)
                    >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK)) // COUNT_MASK 为 00011111111111111111111111111111
                    return false;
                // 增加 工作线程数
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                // 再次 检查 状态
                if (runStateAtLeast(c, SHUTDOWN))
                    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 c = ctl.get();
										// 如果 线程池正在运行中,或者 当前的额 任务是空的
                    if (isRunning(c) ||
                        (runStateLessThan(c, STOP) && firstTask == null)) {
                        if (t.getState() != Thread.State.NEW)
                            throw new IllegalThreadStateException();
                        // 添加 worker 到工作列表
                        workers.add(w);
                        workerAdded = true;
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                    }
                } finally {
                    mainLock.unlock();
                }
                // 开启线程
                if (workerAdded) {
                    container.start(t);
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
		            // 处理失败
                addWorkerFailed(w);
        }
        return workerStarted;
    }

当执行 完成 container.start(t); 后,worker 中的 线程 就被 开启了, worker 中线程的创建方式如下getThreadFactory().newThread(this); worker 本身实现了 Runnable接口,所以 开启线程后,就会执行 worker 中的 run ——> runWorker(this); 方法。

runWorker方法:执行任务的核心

  1. 获取到 当前 worker 的 第一个任务(即 提交任务时的 任务)
  2. 清空 worker 中 所有的 锁(worker 自己实现的锁,直接释放 所有资源)
  3. 进入循环(当前任务 不为空,或者 从 等待队列中获取的任务不为空)
    1. 当前 worker 加锁
    2. 如果当前线程池状 大于等于 STOP ,或者 当前线程被中断过并且线程池状态 大于等于 STOP , 并且 当前线程没有发生中断,则触发 中断
    3. 执行 beforeExecute 方法
    4. 执行任务的
    5. 执行 afterExecute 方法
    6. 累加 当前worker 完成的 任务数
    7. 解锁 当前 worker
  4. 执行 processWorkerExit 方法,做 线程退出后的后置处理
    1. 获得 mainLock
    2. 累加 完成任务数
    3. workers 中移除 该 worker
    4. 解锁
  5. 尝试 关闭线程池
  6. 如果 当前线程池 还在运行中
    1. 如果 是因为 异常 退出,则 新添加 一个 空的线程
    2. 如果是正常退出,则判定 当前 线程池中核心线程 的数量
      1. 如果 当前队列有任务,并且 线程数为空,则会 新增一个线程

getTask 方法 详解

getTask 方法的 作用是 从 等待队列中 获取任务信息。

  1. 进入循环
    1. 判断当前线程池状态,如果当前线程池状态包括 SHUTDOWNSTOPTINDYINGTERMINATED并且 是 STOPTINDYINGTERMINATED 或者 任务队列为空,则减少工作线程数,并返回(相当于终结线程)。
    2. 计算线程池数,如果当前线程数 可减少,则释放线程
    3. 从等待 队列中 获取 任务
      1. 如果配置了 最大线程 的存活时间,则 会用指定时间去获取任务,获取不到任务,线程终止
      2. 否则 阻塞 等待并返回 任务
  private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();

            // Check if queue empty only if necessary.
            if (runStateAtLeast(c, SHUTDOWN)
                && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

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

						// 超过了 最大线程池数 或者 配置了时间,并且 等待队列为空,则 减少线程
            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;
            }
        }
    }

shutDown、terminal 等方法详解

shutDown 方法

shutDown 不会 立即 关闭线程池,会启动一个 有序 关停程序。具体流程如下:

  1. 获得 mainLock
  2. 检查 是否有关停权限
  3. 死循环中 修改 线程池状态为 SHUTDOWN
  4. 调用 interruptIdleWorkers 方法 ,中断空闲线程
    1. 获取 mainLock
    2. workers 开始遍历 所有 的 worker
      1. 如果 线程 没有发生过中断,并且 获取 worker的 锁成功(说明 没有在执行任务,执行任务时,需要获取锁),则发出中断
    3. 释放 mainLock
  5. 执行 onShutdown() 回调钩子
  6. 释放 mainLock
  7. 执行 tryTerminat() 方法

shutDownNow()方法

shutDown 方法 不一样,该 方法 会立即 停止,并且 停止 接受 新的 任务,并且会中断正在执行的任务。流程如下:

  1. 获取 mainLock 的锁
  2. 检查是否有权限 关停
  3. 将 线程池状态 设置为 STOP :不接受 新任务、不处理 队列中的任务,并且中断 正在执行的任务
  4. 调用 interruptWorkers 方法,中断 所有的线程:
    1. 调用 worker 中的 interruptIfStarted ,只要线程是开启状态,并且 没有发生过中断,则发出中断。
  5. 调用 drainQueye 方法,清空所有正在等待的任务。
  6. 释放锁
  7. 返回 所有未执行的任务

tryTerminate() 方法

tryTerminate 方法 会尝试 将 线程池 状态 先变为 TIDYING ,然后 将状态变更为 TERMINATED 。具体流程如下(以下流程 均是在一个死循环中):

  1. 如果当前 线程池满足以下情况之一,则直接返回:
    1. 线程池正在运行
    2. 线程池 处于 TIDYING 或者 TERMINATED 状态:说明正在被终止
    3. 处于 SHUTDOWN RUNNING 状态,并且等待队列不为空: 说明 正在运行 或者 任务没有处理完
  2. 如果当前的工作线程数 大于0,则调用 interruptIdleWorkers 中断第一个 worker
  3. 获取 mainLocl
  4. 更改 线程池 状态为 TIDYING ,更新 成功则:
    1. 调用 terminated 方法: ThreadPoolExecutor 中为空shi'xian
    2. 设置线程池状态为 TERMINATED 状态
    3. 通知 监听 termination 的线程:awaitTermination() 方法会监听
    4. 关闭 container

总结

通过上诉分析,可以很 清楚 的知道,创建线程池时,每一个 参数的作用过程:

  1. maximumPoolSize:最大线程数

  2. workQueue:等待队列。加入 任务的时候,如果线程达到了最大核心 线程数,则会添加到此队列中

  3. threadFactory:线程工厂

  4. handler:拒绝策略Handler

  5. keepAliveTime:线程存活时间,还有一个 unit 为单位

  6. corePoolSize:核心线程数

  7. 提交任务时,如果不满足 核心线程数量,则创建线程: corePoolSize

  8. 如果 满足 核心线程数量,则 加入到等待队列: workQueue

  9. 加入等待 队列 失败,则 使用 addWorker 方法 ,判定 是否满足最大 线程数: maximumPoolSize

  10. 加入失败,则 使用 拒绝策略: handler

  11. 加入成功,则 开启线程,执行任务,执行完成后 通过 getTask 方法 获取 队列中的任务: workQueue

    1. 获取任务时,判断线程数量是否可减少
      1. 如果大于 最大线程数,则减少 线程: maximumPoolSize
      2. 如果当前线程数 大于 核心线程数,并且 获取任务 超时,则减少 线程:corePoolSizemaximumPoolSizekeepAliveTime

worker中 锁的作用

  1. 避免 被意外中断:tryTerminate 会尝试 中断 空闲线程需要申请 锁,worker 在执行任务时 会去 申请锁,通过锁的方式,保证 不会被意外中断。
  2. 通过 锁中的 state 判断,线程是否启动(因为启动的时候会加锁,所以state 的值 大于0)