Bootstrap

Java 线程池ThreadPoolExecutor

什么是线程池

  1. 为了避免系统频繁的创建和销毁线程,我们可以让创建的线程复用
  2. 在线程池当中总有几个活跃的线程,在需要使用线程的时候拿到一个空闲的线程,当工作完成了的时候,并不急这把这个线程关掉,而是把线程归还到线程池当中,方便给他人使用
  3. 总而言之,在使用线程池之后,开启线程就变成了在线程池当中找到一个空闲的线程,销毁线程变成了归还线程到线程池的过程

特点

  1. 控制最大线程并行数量
  2. 线程复用
  3. 管理线程

工作原理

在这里插入图片描述
举个例子:假如CorePool等于10,maxnumPoolSize=50,阻塞队列用ArrayBlockingQueue(50)

  1. 向线程池提交一个线程,先判断(工作的线程是否超过CorePool这里的corePool是10)
  2. 如果没有超过,会通过ThreadFactory创建一个新的xianc(被worker包装),然后持续任务
  3. 如果超过了,则会加入到阻塞队列里面去进行排队(这里阻塞队列用的的ArrayBlockingQueue长度为50)
  4. 如果队列满了(也就是超过了50),则会去判断当前的工作线程是否小于maxnumPoolSize(这里maxnumPoolSize是为50)如果小于则会,继续创建线程(包装成worker对象)
  5. 如果maxnumPoolSize也超过了的话就会执行拒绝策略。

ThreadPoolExecutor 构造函数

    public ThreadPoolExecutor(int corePoolSize, // 核心线程数
                              int maximumPoolSize, // 最大线程数
                              long keepAliveTime, // 当线程数量大于核心线程数时, 空闲线程最大的生存期为keepAliveTime
                              TimeUnit unit, // keepAliveTime的时间单位
                              BlockingQueue<Runnable> workQueue, // 待执行的任务队列
                              ThreadFactory threadFactory, // 用于创建新线程的工厂类
                              RejectedExecutionHandler handler) { // 由于达到线程边界和队列容量而无法执行时使用的拒绝策略实现类

    }

  • corePoolSize(必需):核心线程数。即池中一直保持存活的线程数,即使这些线程处于空闲。但是将allowCoreThreadTimeOut(必需)参数设置为true后,核心线程处于空闲一段时间以上,也会被回收。
  • maximumPoolSize(必需):最大线程数量。当核心线程满了并且阻塞队列也满了之后,线程池会临时追加创建线程,直到线程数达到maximumPoolSize值的上限
  • keepAliveTime(必需):当线程数量大于核心线程数时, 空闲线程最大的生存期为keepAliveTime,该线程将被回收。将allowCoreThreadTimeOut参数设置为true后,核心线程也会被回收。
  • unit(必需):时间单位
  • workQueue(必需):任务队列,采用阻塞队列实现
  • threadFactory(可选):线程工厂。指定线程池创建线程的方式。
  • handler(可选):拒绝策略。当线程池中线程数达到maximumPoolSize且workQueue打满时,后续提交的任务将被拒绝,handler可以指定用什么方式拒绝任务。

阻塞任务队列

使用ThreadPoolExecutor需要指定一个实现了BlockingQueue接口的任务等待队列。在ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们是:SynchronousQueueLinkedBlockingQueueArrayBlockingQueue

  1. SynchronousQueue:是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。
  2. LinkedBlockingQueue:无界队列(严格来说并非无界,上限是Integer.MAX_VALUE),基于链表结构。使用无界队列后,当核心线程都繁忙时,后续任务可以无限加入队列,因此线程池中线程数不会超过核心线程数。这种队列可以提高线程池吞吐量,但代价是牺牲内存空间,甚至会导致内存溢出。另外,使用它时可以指定容量,这样它也就是一种有界队列了。
  3. ArrayBlockingQueue:有界队列,基于数组实现。在线程池初始化时,指定队列的容量,后续无法再调整。这种有界队列有利于防止资源耗尽,但可能更难调整和控制。

另外,Java还提供了另外4种队列:

  1. PriorityBlockingQueue:支持优先级排序的无界阻塞队列
  2. DelayQueue:延迟队列。基于二叉堆实现,同时具备:无界队列、阻塞队列、优先队列的特征。
  3. LinkedBlockingDeque:双端队列。基于链表实现,既可以从尾部插入/取出元素,还可以从头部插入元素/取出元素。
  4. LinkedTransferQueue:由链表结构组成的无界阻塞队列。这个队列比较特别的时,采用一种预占模式,意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,那就生成一个节点(节点元素为null)入队,然后消费者线程被等待在这个节点上,后面生产者线程入队时发现有一个元素为null的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素。

拒绝策略

  1. AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。
  2. CallerRunsPolicy
  3. DiscardPolicy:直接丢弃任务,不抛出任何异常。
  4. DiscardOldestPolicy:将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行。

线程池状态

在这里插入图片描述

Executors封装线程池

在这里插入图片描述

  1. FixedThreadPool: 创建固定容量线程池。特点是核心线程数等于最大线程数,意味着线程池只能创建核心线程,keepAliveTime为0,即线程执行完任务立即回收。任务队列未指定容量,代表使用默认值Integer.MAX_VALUE。适用于需要控制并发线程的场景。
  2. SingleThreadExecutor: 单线程线程池。特点是线程池中只有一个线程(核心线程),线程执行完任务立即回收,使用有界阻塞队列(容量未指定,使用默认值Integer.MAX_VALUE)
  3. ScheduledThreadPool: 定时线程池。指定核心线程数量,普通线程数量无限,线程执行完任务立即回收,任务队列为延时阻塞队列。这是一个比较特别的线程池,适用于执行定时或周期性的任务。
  4. CachedThreadPool: 缓存线程池。没有核心线程,普通线程数量为Integer.MAX_VALUE(可以理解为无限),线程闲置60s后回收,任务队列使用SynchronousQueue这种无容量的同步队列。适用于任务量大但耗时低的场景。

源码解读

execute

在这里插入图片描述

    public void execute(Runnable command) {
        if (command == null) {
            throw new NullPointerException();
        }

        /** ctl记录着workCount和runState */
        int c = ctl.get();

        /** case1: 如果线程池中的线程数量小于核心线程数,那么创建线程并执行*/
        if (workerCountOf(c) < corePoolSize) { // workerCountOf(c): 获取当前活动的线程数
            /**
             * 在线程池中新建一个新的线程
             * command:需要执行的Runnable线程
             * true:新增线程时,【当前活动的线程数】是否 < corePoolSize
             * false:新增线程时,【当前活动的线程数】是否 < maximumPoolSize
             */
            if (addWorker(command, true)) {
                // 添加新线程成功,则直接返回。
                return;
            }
            // 添加新线程失败,则重新获取【当前活动的线程数】
            c = ctl.get();
        }

        /** 第二步:如果当前线程池是运行状态 并且 任务添加到队列成功
         * (即:case2: 如果workCount >= corePoolSize,创建线程往workQueue添加线程任务,等待执行)*/
        // BlockingQueue<Runnable> workQueue 和 Runnable command
        if (isRunning(c) && workQueue.offer(command)) { // 添加command到workQueue队列中。
            // 重新获取ctl
            int recheck = ctl.get();
            // 再次check一下,当前线程池是否是运行状态,如果不是运行时状态,则把刚刚添加到workQueue中的command移除掉,并调用拒绝策略
            if (!isRunning(recheck) && remove(command)) {
                reject(command);
            } else if (workerCountOf(recheck) == 0) { // 如果【当前活动的线程数】为0,则执行addWork方法
                /**
                 * null:只创建线程,但不去启动
                 * false:添加线程时,根据maximumPoolSize来判断
                 *
                 * 如果 (workerCountOf(recheck) > 0, 则直接返回,在队列中的command稍后会出队列并且执行
                 */
                addWorker(null, false);
            }
        }

        /**
         * 第三步:满足以下两种条件之一,进入第三步判断语句
         *  case1: 线程池不是正在运行状态,即:isRunning(c)==false
         *  case2: workCount >= corePoolSize 并且 添加workQueue队列失败。即:workQueue.offer(command)==false
         *
         * 由于第二个参数传的是false,所以如果workCount < maximumPoolSize,则创建执行线程;否则,进入方法体执行reject(command)
         */
        else if (!addWorker(command, false)) {
            // 执行线程创建失败的拒绝策略
            reject(command);
        }
    }

基本过程:

  1. 根据ctl来获取workcount和runState
  2. 如果ctl小于核心线程数那么会调用addWorke增加一个工作线程并添加任务
  3. 如果ctl大于核心线程数那么会判断当前线程池是运行状态 并且 任务添加到队列成功
  4. 放入成功后再次判断工作线程数是否是0,如果是则执行addWork方法,否则直接返回
  5. 最后一种情况是当线程池不是运行状态且任务添加到队列失败,则执行拒绝策略

addWorker(Runnable firstTask, boolean core) 方法

参数说明:
  1. Runnable firstTask:新创建的线程应该首先运行的任务(如果没有,则为空)。
  2. boolean core:该参数决定了线程池容量的约束条件,即当前线程数量以何值为极限值。参数为 true 则使用corePollSize 作为约束值,否则使用maximumPoolSize。

在这里插入图片描述

private boolean addWorker(Runnable firstTask, boolean core) {
    // 外层循环:判断线程池状态
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        /** 
         * 1.线程池为非Running状态(Running状态则既可以新增核心线程也可以接受任务)
         * 2.线程为shutdown状态且firstTask为空且队列不为空
         * 3.满足条件1且条件2不满足,则返回false
         * 4.条件2解读:线程池为shutdown状态时且任务队列不为空时,可以新增空任务的线程来处理队列中的任务
         */
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

		// 内层循环:线程池添加核心线程并返回是否添加成功的结果
        for (;;) {
            int wc = workerCountOf(c);
            // 校验线程池已有线程数量是否超限:
            // 1.线程池最大上限CAPACITY 
            // 2.corePoolSize或maximumPoolSize(取决于入参core)
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize)) 
                return false;
            // 通过CAS操作使工作线程数+1,跳出外层循环
            if (compareAndIncrementWorkerCount(c)) 
                break retry;
            // 线程+1失败,重读ctl
            c = ctl.get();   // Re-read ctl
            // 如果此时线程池状态不再是running,则重新进行外层循环
            if (runStateOf(c) != rs)
                continue retry;
            // 其他 CAS 失败是因为工作线程数量改变了,继续内层循环尝试CAS对线程数+1
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    /**
     * 核心线程数量+1成功的后续操作:添加到工作线程集合,并启动工作线程
     */
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        final ReentrantLock mainLock = this.mainLock;
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            // 下面代码需要加锁:线程池主锁
            mainLock.lock(); 
            try {
                // 持锁期间重新检查,线程工厂创建线程失败或获取锁之前关闭的情况发生时,退出
                int c = ctl.get();
                int rs = runStateOf(c);

				// 再次检验线程池是否是running状态或线程池shutdown但线程任务为空
                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();  // 释放锁
            }
            if (workerAdded) { // 工作线程添加成功,启动该线程
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        //线程启动失败,则进入addWorkerFailed
        if (! workerStarted) 
            addWorkerFailed(w);
    }
    return workerStarted;
}

解读

1.外层循环判断线程池的状态是否可以新增工作线程。这层校验基于下面两个原则:

  1. 线程池为Running状态时,既可以接受新任务也可以处理任务
  2. 线程池为Stop状态时只能新增空任务的工作线程(worker)处理任务队列(workQueue)中的任务不能接受新任务

2.内层循环向线程池添加工作线程并返回是否添加成功的结果。

  1. 之后校验线程数是否已经超限制,是则返回false,否则进入下一步
  2. 通过CAS使工作线程数+1,成功则进入步骤3,失败则再次校验线程池是否是运行状态,是则继续内层循环,不是则返回外层循环

3.核心线程数量+1成功的后续操作:添加到工作线程集合,并启动工作线程

  1. 首先获取锁之后,再次校验线程池状态(具体校验规则见代码注解),通过则进入下一步,未通过则添加线程失
  2. 线程池状态校验通过后,再检查线程是否已经启动,是则抛出异常,否则尝试将线程加入线程池
  3. 检查线程是否启动成功,成功则返回true,失败则进入 addWorkerFailed 方法

addWorkerFailed

    /**
     * 回滚创建的工作线程
     * 1> 从workers队列中移除worker(如果存在队列中的话)。
     * 2> workerCount减一。
     * 3> 重新检查是否终止(termination),以防止这个worker的存在阻止了终止(termination)
     */
    private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (w != null) {
                workers.remove(w); // 步骤1:从work队列中移除w
            }
            // 步骤2:workerCount减1
            decrementWorkerCount();
            // 步骤3:
            tryTerminate();
        } finally {
            mainLock.unlock();
        }
    }

    /**
     * 如果(state是SHUTDOWN状态,并且线程池和队列都为空)或者(state是STOP状态,并且线程池和队列都为空),则将state状态转换为TERMINATED。
     * 如果可以终止,但workerCount非零,则中断空闲的worker,以确保关闭信号传播。
     * 必须在可能终止操作的任何操作之后调用此方法-减少worker计数或在关闭过程中从队列中删除任务。
     * 该方法是非私有的,以允许从ScheduledThreadPoolExecutor访问。
     */
    final void tryTerminate() {
        for (; ; ) {
            int c = ctl.get();
            /**
             * 只有runState=STOP 或 SHUTDOWN(且workQueue为空)才能继续往下执行
             */
            if (isRunning(c) || // 判断c是否是RUNNING状态
                    runStateAtLeast(c, TIDYING) || // 判断c是否是TIDYING或者TERMINATED
                    (runStateOf(c) == SHUTDOWN && !workQueue.isEmpty())) { // 判断如果c是否是SHUTDOWN状态并且workQueue不为空
                return;
            }
            // 当前活动的线程数不为0
            if (workerCountOf(c) != 0) { // Eligible(有资格的) to terminate
                // 因为ONLY_ONE=true,所以只针对一个闲置线程执行interrupt()
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { // 尝试将当前线程池的运行状态设置为TIDYING,活跃线程数量设置为0
                    try {
                        terminated(); // 空方法
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0)); // 将当前线程池的运行状态设置为TERMINATED,活跃线程数量设置为0
                        termination.signalAll(); // 唤醒所有线程
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
        }
    }

流程
  1. 把worker吧workers移除
  2. workerCount -1
  3. 重新检查是否终止(termination),以防止这个worker的存在阻止了终止(termination)。

runWorker

在这里插入图片描述

1、判断当前任务或者从任务队列中获取的任务是否不为空,都为空则进入步骤2,否则进入步骤3

2、任务为空,则将completedAbruptly置为false(即线程不是突然终止),并执行processWorkerExit(w,completedAbruptly)方法进入线程退出程序

3、任务不为空,则进入循环,并加锁

4、判断是否为线程添加中断标识,以下两个条件满足其一则添加中断标识:

  • 线程池状态>=STOP,即STOP或TERMINATED
  • 一开始判断线程池状态<STOP,接下来检查发现Thread.interrupted()为true,即线程已经被中断,再次检查线程池状态是否>=STOP(以消除该瞬间shutdown方法生效,使线程池处于STOP或TERMINATED)

5、执行前置方法 beforeExecute(wt, task)(该方法为空方法,由子类实现)后执行task.run() 方法执行任务(执行不成功抛出相应异常)

6、执行后置方法 afterExecute(task, thrown)(该方法为空方法,由子类实现)后将线程池已完成的任务数+1,并释放锁。

7、再次进行循环条件判断。

processWorkerExit()

processWorkerExit(Worker w, boolean completedAbruptly)执行线程退出的方法

参数说明:
  1. Worker w:要结束的工作线程。
  2. boolean completedAbruptly: 是否突然完成(异常导致),如果工作线程因为用户异常死亡,则completedAbruptly参数为 true
/**
 * Performs cleanup and bookkeeping for a dying worker. Called
 * only from worker threads. Unless completedAbruptly is set,
 * assumes that workerCount has already been adjusted to account
 * for exit.  This method removes thread from worker set, and
 * possibly terminates the pool or replaces the worker if either
 * it exited due to user task exception or if fewer than
 * corePoolSize workers are running or queue is non-empty but
 * there are no workers.
 *
 * @param w the worker
 * @param completedAbruptly if the worker died due to user exception
 */
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    /**
     * 1.工作线程-1操作
     * 1)如果completedAbruptly 为true,说明工作线程发生异常,那么将正在工作的线程数量-1
     * 2)如果completedAbruptly 为false,说明工作线程无任务可以执行,由getTask()执行worker-1操作
     */
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    // 2.从线程set集合中移除工作线程,该过程需要加锁
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 将该worker已完成的任务数追加到线程池已完成的任务数
        completedTaskCount += w.completedTasks;
        // HashSet<Worker>中移除该worker
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    
	// 3.根据线程池状态进行判断是否结束线程池
    tryTerminate();
	
	/**
     * 4.是否需要增加工作线程
     * 线程池状态是running 或 shutdown
     * 如果当前线程是突然终止的,addWorker()
     * 如果当前线程不是突然终止的,但当前线程数量 < 要维护的线程数量,addWorker()
     * 故如果调用线程池shutdown(),直到workQueue为空前,线程池都会维持corePoolSize个线程,然后再逐渐销毁这corePoolSize个线程
     */
    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);
    }
}

流程

1、如果 completedAbruptly 为 true,即工作线程因为异常突然死亡,则执行工作线程-1操作。

2、主线程获取锁后,线程池已经完成的任务数追加 w(当前工作线程) 完成的任务数,并从worker的set集合中移除当前worker。

3、根据线程池状态进行判断是否执行tryTerminate()结束线程池。

4、是否需要增加工作线程,如果线程池还没有完全终止,仍需要保持一定数量的线程。

  • 如果当前线程是突然终止的,调用addWorker()创建工作线程
  • 当前线程不是突然终止,但当前工作线程数量小于线程池需要维护的线程数量,则创建工作线程。需要维护的线程数量为corePoolSize(取决于成员变量 allowCoreThreadTimeOut是否为 false)或1。

https://blog.csdn.net/qq_32828253/article/details/112297856?spm=1001.2014.3001.5502
https://blog.csdn.net/mu_wind/article/details/113806680

;