深入线程池——线程池非核心线程的创建&销毁时机

341 阅读9分钟

本文章阅读需要简单阅读过ThreadPoolExecutor源码,或对其八股熟悉

1、八股结论

keepAliveTime:线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,多余的空闲线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁,线程池回收线程时,会对核心线程和非核心线程一视同仁,直到线程池中线程的数量等于 corePoolSize ,回收过程才会停止。


原文链接:javaguide.cn/java/concur…

2、调试Demo

package org.example.TestThreadPool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * ---leetcode---
 *
 * @author summer77
 * @date 2024/3/2 16:07
 * <p>
 * ---TestThreadPool---
 */
public class TestThreadPool {
    public static void main(String[] args) {
        
        // 核心线程数量为3,非核心线程数量最多为2,任务队列最多存放2个任务
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                3,  // 核心线程数量
                5,  // 最大线程数量
                60, // 非核心线程空闲时可存活时间
                TimeUnit.SECONDS, // 单位
                new ArrayBlockingQueue<>(2), // 阻塞任务队列大小
                new ThreadPoolExecutor.AbortPolicy()
        );
        
        for (int i = 0; i < 7; i++) {
            int finalI = i + 1;
            executor.execute(()->{
                System.out.println("任务" + finalI + "正在执行...");
            });
        }
    }
}

3、深入源码

3.1、execute()

  • 针对非核心线程,核心线程的创建我们就不细看了
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    
    // 当工作线程数量小于核心线程时,正常创建新的核心线程,不讨论这里
    if (workerCountOf(c) < corePoolSize) {
        // 注意第二个参数 true,他决定着非核心线程的创建
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    
    // 走到这里说明核心线程满了,无法再继续创建核心线程了,这时会将任务添加到任务队列
    // 如果当前线程池仍然是运行态,将任务添加到任务队列中,如果队列已满,会返回false
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        
        // 如果不是运行态则移除刚刚放入阻塞队列的任务并调用饱和策略
        // 这个remove()方法中还会tryTerminate()
        if (! isRunning(recheck) && remove(command))
            reject(command);
        
        // 如果工作线程数量为0,则创建一个工作线程
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    
    // 到这里才是非核心线程的创建
    // 我们的探索的是任务队列已满才来到这里的情况
    // 注意第二个参数false,他决定了非核心线程的创建
    else if (!addWorker(command, false))
        // 添加失败,代表线程池状态不允许新任务添加,或线程数达到了最大线程数
        // 调用饱和策略
        reject(command);
}

3.2、非核心线程的创建——addWorker(task,false)

假定我们这次创建的是非核心线程,参数为false

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

        // Check if queue empty only if necessary.
        // 检查
        if (rs >= SHUTDOWN &&   
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            // 获取工作线程数量
            int wc = workerCountOf(c); 
            
            // 重点来了!
                // 这句是检查工作线程是否大于规定最大容量
            if (wc >= CAPACITY ||
            
                // 注意这个core,就是我们传入的参数false
                // core为true,判断逻辑为 wc >= corePoolSize,如果大于核心线程数就直接return false了
                // core为false,判断逻辑为wc >= maximumPoolSize,如果大于最大线程数return false
                // 如果当前小于maximumPoolSize,我们就有机会创建非核心线程了
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            
            // 能走到这里说明满足新线程创建条件了。
            // ctl低位+1 (也就是工作线程数+1)
            // 如果自增失败,代表有竞争,检查后重试即可。
            // 如果自增成功,则break
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                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);
        // 获取Worker中新建的线程
        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());  
                
                // 再次检查运行状态
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {   
                    
                    // 检查线程存活状态
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    
                    // 将worker加入工作线程集合,这是一个HashSet
                    workers.add(w);  
                    int s = workers.size();
                    if (s > largestPoolSize)
                        //记录线程池中的最大工作线程数,可用于观测线程池运行情况
                        largestPoolSize = s;  
                    // 加入成功后标识为true
                    workerAdded = true;  
                }
            } finally {
                mainLock.unlock();
            }
            // 如果工作线程创建添加成功,start线程
            if (workerAdded) {  
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);  //如果线程启动失败,这里会从workers中移除添加的worker,并且尝试终止线程池
    }
    return workerStarted; //返回值即可看作线程是否创建、添加、启动全部成功
}

接下来就是探寻工作线程如何执行任务

3.3、runWorker()

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    
    // 这里获取的是第一次创建Worker时分配的第一个任务,将他存在了task上
    Runnable task = w.firstTask;  
    
    //重置为null,为后续循环获取新的任务做准备
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        //如果初始任务还没执行,先进入循环块执行初始任务。如果执行过了,则循环调用getTask()方法,获取新的任务
        //我们主要关注点是getTask方法
        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
            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;  //task设为空,保证循环获取新任务
                w.completedTasks++;  //记录已经执行的数量
                w.unlock();  //解锁
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly); //如果被打断了会把线程数量减到0
    }
}

3.4、getTask()

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.
         //如果线程池状态为SHUTDOWN且任务队列为空,或线程池已经处于不处理新任务的更高结束态
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            // 将线程数量减到0 ,操作的是ctl,实际线程此时还没有被销毁
            decrementWorkerCount();  
            return null;
        }
        
        // 获取线程数量
        int wc = workerCountOf(c);

        // Are workers subject to culling?
        
        // 重点开始了
        // allowCoreThreadTimeOut默认值是false,它是通过allowCoreThreadTimeOut()方法去改变的,一会再说实际作用
        // 我们这里先看allowCoreThreadTimeOut为false的情况
        // 如果当前工作线程数已经大于核心线程数,timed为true
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;  
        
        // 第一次来timeOut是false,暂时不会进入下面这个if块
        if ((wc > maximumPoolSize || (timed && timedOut)) 
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 尝试从任务队列中获取任务
            // 此时timed为true,执行的是poll方法
            Runnable r = timed ?
                // 参数:keepAliveTime
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :  
                workQueue.take(); //无救急线程时调用,直接返回
            if (r != null)
                return r;  //执行成功则直接return
            timedOut = true;  //超时,没拿到任务,救急线程太闲了该死了
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    // 转为nanos
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 如果此时任务队列中任务数量为0,进入等待逻辑
        while (count == 0) {
            if (nanos <= 0)
                // 等待超时,返回值为null
                return null;
                
            // 等待...返回值为剩余的时间
            // 如果等待期间内任务队列中添加了新的元素,会被唤醒
            nanos = notEmpty.awaitNanos(nanos);
        }
        
        // 非空则正常获取任务
        return dequeue();
    } finally {
        lock.unlock();
    }
}

假如该工作线程没有获取到新的任务,超时返回NULL了,回到刚刚的主方法

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.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();  
            return null;
        }
        
        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;  
        
        // 此时timed 和 timeOut全部为true,且工作线程数量大于1,条件成立
        if ((wc > maximumPoolSize || (timed && timedOut)) 
            && (wc > 1 || workQueue.isEmpty())) {
            
            // 自减工作线程计数,操作的是ctl,还没有进入销毁环节
            if (compareAndDecrementWorkerCount(c))
                // 返回值为null将控制工作线程的实际销毁
                return null;
            continue;
        }

        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :  
                workQueue.take();
            // 假设返回的是r = null
            if (r != null)
                // 正常获取任务时返回任务对象
                return r;  
                
            //超时标记,没获取任务,进入销毁流程,回到for循环头部
            timedOut = true;  
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        // 此时getTask的结果为null,没办法进入while循环了,将进入finally块
        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
            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 {
        // 销毁线程
        processWorkerExit(w, completedAbruptly);
    }
}

3.5、processWorkerExit()——销毁线程

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        // 被打断的情况下还没有进行ctl自减,需要自减
        decrementWorkerCount();
        
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 把工作线程中的任务完成数量累加到总完成数量上
        completedTaskCount += w.completedTasks;
        // 移除工作线程
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    
    // 尝试终止线程池,如果线程池状态是运行态或者线程池是SHUTDOWN但任务还没处理完是不会终止的
    tryTerminate();
    
    // 下面就是控制最小工作线程数量的逻辑
    int c = ctl.get();
    // 确保当前线程池状态是RUNNING或者SHUTDOWN
    if (runStateLessThan(c, STOP)) {
        // 确保工作线程不是被打断的
        if (!completedAbruptly) {
            // allowCoreThreadTimeOut默认为false
            // 所以这时工作线程最小值就是核心线程数
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            
            // 如果参数为true,且任务队列不为空 最小工作线程数就是1了
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
                
            // 如果当前线程数量大于核心线程数,不用管
            // 如果小于核心线程数,下面会创建新的worker
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        
        // 如果是被打断的,或当前工作线程数量小于最小线程数量
        // 新建工作线程
        addWorker(null, false);
    }
}

经过源码分析,正常情况下如果线程空闲会不分核心非核心线程,会直至减小到corePoolSize数量,但还有另一种情况

4、allowCoreThreadTimeOut(true)——改变最小工作线程数

    executor.allowCoreThreadTimeOut(true);
  1. 默认情况下,allowCoreThreadTimeOutfalse,即核心线程即使在空闲时也会保持存活状态,不会被立即回收。
  2. allowCoreThreadTimeOut 被设置为 true 时,核心线程也会使用 keepAliveTime 来控制等待工作的超时时间,即当核心线程空闲超过一定时间后,会根据 keepAliveTime 来决定是否被回收。
  3. 如果线程都空闲,决定回收,如果任务队列为空,可能会回收全部线程。如果任务队列不为空,可能会保留一个工作线程