ThreadPoolExecutor类(线程池)的介绍和使用


ThreadPoolExecutor简介

可以说ThreadPoolExecutor 就是线程池,实现类ThreadPoolExecutor实现最复杂的运行部分,如下关系图
在这里插入图片描述
ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务,所以我们关注点
1.线程池的生命周期管理
2.线程池管理线程和任务

线程池生命周期管理

ctl变量

线程池运行的状态,并不是显式设置的,像Thread类枚举线程状态值,而是伴随着线程池的运行,由内部计算得来这样维护的。
线程池内部使用一个变量AtomicInteger类型ctl 维护两个值:运行状态(runState)线程数量(workerCount),高3位(左边3位)保存runState,低29位(右边29位)保存workerCount,用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源

public class ThreadPoolExecutor extends AbstractExecutorService {
    
    
	//ctl是一个AtomicInteger类型 计算方法ctlOf(RUNNING, 0) 参数为运行状态 和 线程数量
	private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

	//通过状态和线程数生成ctl 
	private static int ctlOf(int rs, int wc) {
    
     return rs | wc; }
	}

ctlOf(int rs, int wc) 方法解读

  • rs (run state): 表示线程池的状态,占用整数的高3位
  • wc (worker count): 表示线程池中工作线程的数量,占用整数的低29位
  • 位运算符 | 是按位 或 运算符,它将两个整数的每一位进行或运算。如果两个相应的二进制位中至少有一个为1,则结果为1;否则为0

假设 rs 和 wc 的值分别为:

  • rs = 010 (二进制,表示 TIDYING 状态)
  • wc = 0000000000000000000000000000101 (二进制,表示 5 个工作线程数)
    在这里插入图片描述
    最终结果 0100000000000000000000000000101 就是合并后的 ctl 值,其中高3位表示线程池状态,低29位表示工作线程数

线程池工作线程数量

	//Integer.SIZE:32 表示线程池是一个32位的管理单位,COUNT_BITS:29 低29位 (右边29位)
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //定义静态私有化常量CAPACITY
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

CAPACITY:低29位全为1的掩码,值为 (1 << COUNT_BITS) - 1(即 2^29 - 1 = 536,870,911)
CAPACITY = 000 1111111111111111111111111111111 = 536,870,911

类中定义线程数量计算方法

//计算当前线程数量 参数c是ctl变量
private static int workerCountOf(int c)  {
    
     return c & CAPACITY; }

&是一个按位 与 运算符,用于对两个整数的每一位进行逻辑与操作。只有当两个相应的二进制位都为1时,结果才为1,否则为0

c是传入的ctl变量,workerCountOf方法是:我们通过 与& 运算符进行获取 实际的线程工作数量
假设int c =101 0000000000000000000000000000101
计算逻辑是

ctl          :101 0000000000000000000000000000101
CAPACITY     :000 1111111111111111111111111111111
按位与&计算结果:000 0000000000000000000000000000101
由于CAPACITY前三位都是0 所以与ctl按照&计算 高3位都是0
由于CAPACITY29位都是1 所以与ctl按照&计算 后29位只需要看ctl的后29

线程池状态

32位ctl变量,高三位代表RunSate(运行状态)

    // 运行状态runstat 每个状态对应的高3位的不同值
	private static final int RUNNING    = -1 << COUNT_BITS; // 111 000...000
	private static final int SHUTDOWN   =  0 << COUNT_BITS; // 000 000...000
	private static final int STOP       =  1 << COUNT_BITS; // 001 000...000
	private static final int TIDYING    =  2 << COUNT_BITS; // 010 000...000
	private static final int TERMINATED =  3 << COUNT_BITS; // 011 000...000

类中定义了运行状态分5种
在这里插入图片描述
涉及到的方法如下

	//	计算当前运行状态
    private static int runStateOf(int c)     {
    
     return c & ~CAPACITY; }    

反运算符(~)会对操作数的每一位进行取反操作 如011–>110
那么 ~CAPACITY 就变成 111 0000000000000000000000000000000
假设int c =101 0000000000000000000000000000101

ctl          :101 0000000000000000000000000000101
~CAPACITY    :111 0000000000000000000000000000000
按位与&计算结果:101 0000000000000000000000000000000
由于~CAPACITY前三位都是1 所以与ctl按照&计算 高3位只需要看ctl的前3位
由于~CAPACITY29位都是0 所以与ctl按照&计算 后29位都是0

线程池工作状态转换方法

线程池状态转换以及涉及到的方法

状态转化图
在这里插入图片描述
shutdown() 调用关闭线程池 RUNNING → SHUTDOWN
关键方法:advanceRunState(SHUTDOWN)通过 CAS 更新 ctl 的高3位状态为 SHUTDOWN

public void shutdown() {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        advanceRunState(SHUTDOWN); // 状态转换:RUNNING → SHUTDOWN
        interruptIdleWorkers();    // 中断空闲线程
        onShutdown();             // 钩子方法(空实现,可扩展)ThreadPoolExecutor类中定义是由ScheduledThreadPoolExecutor拓展实现的
    } finally {
    
    
        mainLock.unlock();
    }
}

shutdownNow() 强制关闭线程池 RUNNING/SHUTDOWN → STOP
关键方法:advanceRunState(STOP)强制更新状态为 STOP,并中断所有工作线程

public List<Runnable> shutdownNow() {
    
    
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        advanceRunState(STOP);     // 状态转换:RUNNING/SHUTDOWN → STOP
        interruptWorkers();        // 中断所有线程
        tasks = drainQueue();      // 清空任务队列
    } finally {
    
    
        mainLock.unlock();
    }
    tryTerminate();                // 尝试进入终止流程
    return tasks;
}

tryTerminate() 任务和线程清理完成后尝试终止线程池
触发条件:当线程池状态为 SHUTDOWN 或 STOP,且任务队列为空、工作线程数为 0
状态转化:SHUTDOWN/STOP → TIDYING → TERMINATED

final void tryTerminate() {
    
    
    for (;;) {
    
    
        int c = ctl.get();
        // 条件检查:状态为 SHUTDOWN/STOP,且任务队列为空、工作线程数为0
        if (isRunning(c) || 
            runStateAtLeast(c, TIDYING) || 
            (runStateOf(c) == SHUTDOWN && !workQueue.isEmpty())) {
    
    
            return;
        }
        // 若仍有活动线程,尝试中断一个线程后退出
        if (workerCountOf(c) != 0) {
    
    
            interruptIdleWorkers(ONLY_ONE);
            return;
        }
        // 更新状态为 TIDYING
        if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
    
    
            try {
    
    
                terminated(); // 钩子方法,子类可扩展
            } finally {
    
    
                ctl.set(ctlOf(TERMINATED, 0)); // 最终状态:TERMINATED
                termination.signalAll();        // 唤醒等待线程
            }
            return;
        }
    }
}

terminated() 钩子方法,由子类覆盖 TIDYING → TERMINATED

protected void terminated() {
    
     
    // 空方法,子类可覆盖(如记录日志、释放资源)
}

作用:在状态变为 TIDYING 后调用,执行自定义清理逻辑,之后自动进入 TERMINATED

任务管理

ThreadPoolExecutor运行机制图
在这里插入图片描述

任务分配

整个Execute框架在顶级接口Executor中定义了execute方法,在ThreadPoolExecutor实现类中重写实现了该方法,是线程池的主要入口,用户提交了一个任务,之后这个任务将如何都是由这个阶段决定,结合addWorker和runWorker完成线程创建与任务执行

public void execute(Runnable command) {
    
    
    //任务单元 线程是否存在
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get(); // 获取当前ctl值(状态 + 线程数)

    // 1.判断核心线程数是否已达到,如果没达到 尝试添加到核心线程队列中去
    if (workerCountOf(c) < corePoolSize) {
    
    
    // 添加核心线程(第二个参数为true) 该方法返回的是布尔值
        if (addWorker(command, true)) 
            return;
        c = ctl.get(); // 若添加失败(如并发场景),重新获取ctl
    }

    // 2. 当核心线程已满 尝试将任务放入工作队列
    if (isRunning(c) && workQueue.offer(command)) {
    
     // 线程池为RUNNING且入队成功
        // 二次检查状态(防止并发关闭)
        int recheck = ctl.get(); 
        // 若线程池已关闭,回滚队列并拒绝任务
        if (!isRunning(recheck) && remove(command)) 
            reject(command);
         // 若线程数为0(如corePoolSize=0),创建非核心线程
        else if (workerCountOf(recheck) == 0) 
            addWorker(null, false);
    }

    // 3. 如果队列已满,并且当前线程数小于最大线程数(maximumPoolSize)尝试创建非核心线程
    else if (!addWorker(command, false)) // 添加非核心线程(第二个参数为false)
	// 4. 创建非核心线程也失败,触发拒绝策略(队列和线程均满)
      reject(command);
}

线程池分配任务机制:

  1. 核心线程数检查:当任务提交时,首先检查当前工作线程数是否小于核心线程数(corePoolSize)。如果是,则尝试创建新线程执行任务。
  2. 任务入队:如果核心线程已满,尝试将任务放入工作队列。这里需要考虑线程池的状态是否允许入队,比如在SHUTDOWN状态下是否接受新任务。
  3. 非核心线程创建:如果队列已满,并且当前线程数小于最大线程数(maximumPoolSize),则创建非核心线程来执行任务。
  4. 拒绝策略:如果队列和线程数都已满,则执行拒绝策略,比如抛出异常或丢弃任务
    在这里插入图片描述

在这里 还需要解释下两个内容
1.线程池7个参数

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,//非核心线程存活时间单位
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
    
    
   	//核心线程默认情况下会一直存在于线程池中,即使这些核心线程不执行任何任务                          
    private volatile int corePoolSize;
	//线程最大数,当核心线程和队列都满了,会根据线程数是否达到最大线程数,如果没有才会创建非核心线程
    private volatile int maximumPoolSize;
    // 非核心线程数存活时间 在工具类Executors.newCachedThreadPool()方法中默认60秒
    private volatile long keepAliveTime;
    //阻塞队列 核心线程满了会将任务放入阻塞队列中,构造方法会给size值
    private final BlockingQueue<Runnable> workQueue;
    //创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
    private volatile ThreadFactory threadFactory;
    //拒绝策略(handler) 当核心线程 队列 非核心都达到了 在无法接受新任务时候的拒绝新任务方式
    private volatile RejectedExecutionHandler handler;

注意:ThreadPoolExecutor 允许运行时修改参数:

executor.setCorePoolSize(6);      // 调整核心线程数
executor.setMaximumPoolSize(12);  // 调整最大线程数
executor.setKeepAliveTime(30, TimeUnit.SECONDS); // 调整非核心线程存活时间

核心线程和非核心线程在Java中的区别在于它们的生命周期和在线程池中的行为
①核心线程默认情况下会一直存在于线程池中,即使这些核心线程不执行任何任务(铁饭碗)
②非核心线程如果长时间处于闲置状态就会被销毁(临时工)。配置的时间内,非核心线程也会销毁,哪怕任务没有执行完,那没有执行完的任务会进入任务队列
③如果设置了allowCoreThreadTimeOut为true,那么这个配置也会作用于核心线程,核心线程可能被回收,导致实际存活线程数降到 0,但 corePoolSize 的配置值不会改变

2.addWorker方法解读
在满足线程池状态和容量限制的条件下,安全地增加工作线程
addWorker 方法返回值是布尔类型 成功创建并启动线程返回 true,否则返回 false

private boolean addWorker(Runnable firstTask, boolean core)
  • 参数:
    ①firstTask:这是一个Runnable类型的参数,代表了需要执行的第一个任务(Runnable不是线程, Runnable 只是一个任务接口(run方法),Thread才是线程),可以为null,firstTask为null意味着新创建的线程将从任务队列中获取任务来执行

②core:是否以核心线程数(corePoolSize)为上限。true 表示使用核心线程限制,false 表示使用最大线程数(maximumPoolSize)。

  • 返回值:成功创建并启动线程返回 true,否则返回 false

其实现逻辑分三步
1.状态检查: 检查线程池状态是否允许创建新线程

retry: //搭配for (;;) while等使用 代表循环从哪里开始结束
for (;;) {
    
     //for (;;)永真循环或无限循环 遇到break return这类结束循环
    int c = ctl.get();
    int rs = runStateOf(c); //获取状态

    // 检查线程池状态是否允许创建新线程
    if (rs >= SHUTDOWN //状态大于或等于shutdown(开始关闭状态),也就是非Running运行状态
    	 &&
    	 //线程池状态是 SHUTDOWN 但队列中还有任务或者 firstTask 为空
        ! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))
                return false;
}

拒绝条件两个按 &&并且 执行
①左侧的条件好理解 rs >= SHUTDOWN:线程池处于 SHUTDOWN、STOP、TIDYING 或 TERMINATED 状态
②右侧的条件! (rs == SHUTDOWN && firstTask == null &&! workQueue.isEmpty()))
SHUTDOWN状态下,只有当队列非空且需要创建无初始任务的线程时,才允许创建新线程,这里仅仅是允许,不是就创建了新线程

为什么SHUTDOWN状态下,还有能允许创建新线程的情况?
当线程池调用 shutdown() 进入 SHUTDOWN 状态时行为规则:

  • 不再接受新任务:后续提交的任务会被拒绝(触发拒绝策略)。
  • 继续处理队列中的剩余任务:已入队的任务会被现有工作线程处理完毕。
  • 不创建新线程:即使队列中有任务且当前线程数不足,线程池也不会创建新线程。

但是存在一种情况现有线程退出导致工作线程数为 0 时,但是队列中还有任务没有处理完的

  • 工作线程数为 0(例如核心线程数为 0 且 allowCoreThreadTimeOut=true
  • 队列非空且线程池状态为 SHUTDOWN

在 execute() 方法中,若任务入队后检测到工作线程数为 0,会调用 addWorker(null, false) 创建新线程处理队列任务

   // 若线程数为0(如corePoolSize=0),创建非核心线程
   else if (workerCountOf(recheck) == 0) 
      addWorker(null, false);
    }

在 SHUTDOWN 状态下,优先利用现有线程处理队列任务,哪怕队列里面任务比较多线程比较少
也不创建新线程,因为线程池即将终止了
兜底机制:若所有线程意外退出,强制创建一个非核心线程处理剩余任务,防止队列任务“饿死”。

2. 线程数限制检查

for (;;) {
    
    
    int wc = workerCountOf(c);
    // 检查是否超过容量或线程数上限 //core参数选择 corePoolSize 或 maximumPoolSize 作为上限
    if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
        return false;
    // CAS增加工作线程数
    if (compareAndIncrementWorkerCount(c))
        break retry;
    c = ctl.get();
    // 状态变化则重试外层循环
    if (runStateOf(c) != rs)
        continue retry;
}

3. 创建并启动Worker

Worker w = new Worker(firstTask);
Thread t = w.thread;
if (t != null) {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        workers.add(w); // 添加到工作线程集合
    } finally {
    
    
        mainLock.unlock();
    }
    t.start(); // 启动线程
    return true;
}

Worker对象:封装任务和执行线程,继承 AQS 实现不可重入锁。
加锁操作:使用 mainLock 确保对 workers 集合的线程安全访问。
4. 失败回滚

finally {
    
    
       if (! workerStarted)
           addWorkerFailed(w);
     }

回滚逻辑:
①若 Worker 已创建,从 workers 集合中移除
②CAS减少工作线程数(CAS 乐观锁 实现了并发安全特性的原子性)
③尝试终止线程池(tryTerminate())

任务缓冲

任务缓冲模块是线程池能够管理任务的核心部分。线程池的本质是对任务和线程的管理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务
阻塞队列(BlockingQueue):生产者是往队列里添加任务的线程,消费者是从队列里拿任务的线程。阻塞队列就是生产者存任务的容器,而消费者也只从容器里拿任务
在这里插入图片描述
是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景

阻塞队列的类型
1.ArrayBlockingQueue:这是一个基于数组的有界阻塞队列,此队列遵循FIFO先进先出原则
2.LinkedBlockingQueue:这是一个基于链表的阻塞有界队列,此队列遵循FIFO先进先出原则,此队列默认长度Integer.MAX_VALUE,所以默认创建此队列有容量风险
3.PriorityBlockingQueue:这是一个基于优先级排序的无界阻塞队列。元素按照优先级进行排序,优先级高的元素会优先被取出,也可以自定义compareTo()方法进行优先级排序,不保证同一优先级的元素顺序
4.SynchronousQueue:这不是一个真正的队列,而是一个不存储元素的传递队列。每个 put 操作必须等待一个 take 操作,否则不能添加新元素,反之亦然。它主要用于在线程间进行直接传递数据的场景
5.DelayQueue:这是一个使用Delayed元素的无界阻塞队列。队列中的元素只有在其延迟到期后才能被消费
6.LinkedTransferQueue:这是一个由链表结构组成的无界阻塞队列,相比其他队列,多了tranfer和tryTranfer方法
7.LinkedBlockingDeque:这是一个由链表结构组成的双向阻塞队列,队头和队尾都可以添加 移除元素,多线程并发时 可以将锁的竞争降低到一半

任务获取

线程池对于任务管理包括
1.核心线程数和非核心线程在创建线程之后,就会启动线程工作,执行(Runnable firstTask)任务
实现逻辑就是上面讲的在addWorker方法中

    private boolean addWorker(Runnable firstTask, boolean core) {
    
    
                if (workerAdded) {
    
    
                    t.start();
                    workerStarted = true;
                }
           }

2.对于阻塞队列中的任务是如何进行获取任务和执行的,这里是如何进行管理的
getTask()方法是工作线程(Worker)从任务队列获取任务的核心逻辑,负责管理线程的生命周期和任务的获取策略
1.

private Runnable getTask() {
    
    
    boolean timedOut = false; // 标记上次poll是否超时

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

        // 1. 状态检查:线程池已关闭且队列为空,或状态为STOP及以上
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
            decrementWorkerCount(); // 减少工作线程数
            return null; // 触发线程退出
        }
		// 当前工作线程数
        int wc = workerCountOf(c);

        // 2. 判断是否允许超时(非核心线程或允许核心线程超时)
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 3. 检查是否需要回收线程:
        //    a. 线程数超过最大值(因配置动态调整)
        //    b. 允许超时且上次poll超时
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
    
    
            // CAS减少线程数,返回null触发线程退出
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue; // CAS失败则重试
        }

        try {
    
    
            // 4. 从队列获取任务:超时等待或阻塞等待
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r; // 成功获取任务
            timedOut = true; // 标记超时
        } catch (InterruptedException retry) {
    
    
            timedOut = false; // 中断后重置超时标记
        }
    }
}

步骤总结
1. 线程池状态检查
条件:若线程池状态为 SHUTDOWN 且队列为空,或状态为 STOP/TIDYING/TERMINATED
行为:减少工作线程数,返回 null,触发当前线程退出
目的:确保关闭状态下不再处理新任务,队列为空时终止线程

2. 超时允许判断
timed 计算:

  • allowCoreThreadTimeOut=true:核心线程允许超时回收。
  • wc > corePoolSize:当前线程数超过核心线程数,视为非核心线程。

结果:timed的值取决于 后续使用 poll()(超时获取)还是 take()(阻塞获取)

3. 线程回收条件
条件:

  • 线程数超过最大值:可能因动态调整 maximumPoolSize 导致。
  • 允许超时且上次获取任务超时(timed && timedOut)。

行为:通过 CAS 减少工作线程数,返回 null 终止当前线程。
目的:动态回收空闲或多余的线程,优化资源使用
4. 从队列获取任务
策略:
timed=true:调用 poll(keepAliveTime, TimeUnit),超时返回 null。
timed=false:调用 take(),阻塞直到获取任务或中断。

异常处理:若捕获 InterruptedException,重置超时标记并重试

任务拒绝

任务拒绝是线程池的保护部分,线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池

拒绝策略是一个接口

public interface RejectedExecutionHandler {
    
    
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

其实现JDK有4个类,所以我们可以自定义实现拒绝策略,或使用自带的4种拒绝策略
在这里插入图片描述
自带的4种拒绝策略说明
1.AbortPolicy类
也是默认使用的拒绝策略

    public static class AbortPolicy implements RejectedExecutionHandler {
    
    
        public AbortPolicy() {
    
     }

        //直接抛出RejectedExecutionException异常,并终止新任务的提交
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

AbortPolicy 通过在 rejectedExecution 方法中抛出 RejectedExecutionException 异常来终止新任务的提交。关键业务推荐使用,严格监控任务提交失败

2.DiscardPolicy类
静默丢弃,允许任务丢失的非关键场景可以使用

    public static class DiscardPolicy implements RejectedExecutionHandler {
    
    
        public DiscardPolicy() {
    
     }

        //终止新任务的提交 但是不抛出异常
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
        }
    }

3.DiscardOldestPolicy类

    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    
    

        public DiscardOldestPolicy() {
    
     }

        //丢弃队列头部任务并重试提交
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
            if (!e.isShutdown()) {
    
    
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

优先处理新任务,容忍旧任务丢失,但可能丢失重要任务,慎重使用

4.CallerRunsPolicy类

    public static class CallerRunsPolicy implements RejectedExecutionHandler {
    
    
        public CallerRunsPolicy() {
    
     }
		
		//调用者线程(提交任务的线程)处理该任务
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
            if (!e.isShutdown()) {
    
    
                r.run();
            }
        }
    }

优点是:控制提交速度,避免过载
缺点是:可能阻塞调用者线程

猜你喜欢

转载自blog.csdn.net/weixin_44891364/article/details/146287109
今日推荐