从源码层面了解java线程池工作机制

线程池的作用

  1. 线程池可以避免重复创建和销毁线程,减少系统开销。
  2. 线程池预先创建一定数量的线程,任务调度更快
  3. 可以限制同时运行的线程数,防止资源耗尽
  4. 支持任务的排队、调度、取消等操作,方便管理
  5. 封装复杂的线程管理细节,开发者只需提交任务

线程池工作机制

先来看一下它的构造方法中有哪些参数

java 复制代码
public ThreadPoolExecutor(int corePoolSize,   //核心线程数
                          int maximumPoolSize,  //最大线程数
                          long keepAliveTime,   //空闲超时时长
                          TimeUnit unit,   //超时时间单位
                          BlockingQueue<Runnable> workQueue,   //任务队列
                          ThreadFactory threadFactory,   // 线程工厂
                          RejectedExecutionHandler handler) {  //拒绝策略
    if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
  1. 线程池接收任务时先交给核心线程进行处理
  2. 当核心线程都在处理任务时会将处理任务加入任务队列
  3. 当任务队列已满时会进行创建非核心线程进行处理任务,直至线程池中的线程数达到最大线程数为止
  4. 当线程池的线程数达到最大线程数且任务队列已满时会触发拒绝策略

线程池的状态:

java 复制代码
//所有高位为1,表示线程池正在运行,111...
private static final int RUNNING = -1 << COUNT_BITS;
//高位都是0,线程池关闭但可处理已提交任务,000...
private static final int SHUTDOWN = 0 << COUNT_BITS;
//停止状态,不处理已提交的任务,切正在运行的任务也会被中断,001...
private static final int STOP = 1 << COUNT_BITS;
//所有已经处理结束或被取消,运行结束,等待清理资源状态,010...
private static final int TIDYING = 2 << COUNT_BITS;
//彻底关闭,所有清理工作结束,011...
private static final int TERMINATED = 3 << COUNT_BITS;

1 提交任务

线程池有两个方法可以提交任务,一个是execute(),还有一个submit()方法 源码如下

java 复制代码
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
        
    int c = ctl.get();
    // 如果线程池中的线程数量小于coolPoolSize
    if (workerCountOf(c) < corePoolSize) {
        // 添加一个线程,并且把提交过来的任务当成firstTask
        if (addWorker(command, true))
            return;
        // 因为线程池的状态和运行的线程数量可能随时都会改变,所以要对线程池时刻进行检查
        c = ctl.get();
    }
    // 进入这个判断是因为上面的判断不符合条件,要么是corePoolSize已达上限,要么是添加线程失败
    // 那么就要进行入队操作,入队操作之前要先判断线程池的状态
    if (isRunning(c) && workQueue.offer(command)) {
        // 再次获取ctl值,时时刻刻做判断
        int recheck = ctl.get();
        // 如果线程池不在运行状态,那么就会执行后面的remove操作,相当于一次回滚,这次执行的任务remove掉
        if (!isRunning(recheck) && remove(command))
            // 执行拒绝策略
            reject(command);
            // 走到这里说明线程池处于执行状态
        else if (workerCountOf(recheck) == 0)
            // 工作线程为0,创建一个非核心线程,防止存在有任务但是没有线程执行的情况
            addWorker(null, false);
    }
    // 创建一个非核心线程进行处理任务,创建新的线程失败了,直接拒绝
    else if (!addWorker(command, false))
        reject(command);
}
java 复制代码
// 调用父类AbstractExecutorService进行提交任务
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    // 将任务封装为 RunnableFuture<T> 类
    RunnableFuture<T> ftask = newTaskFor(task);
    // 调用线程池的execute()执行任务
    execute(ftask);
    // 返回结果
    return ftask;
}

我们可以看到submit()和execute()如下同:

  1. submit()的参数类型为Callable类型,execute()参数类型为Runable
  2. submit()提交的任务可以返回任务执行的结果,execute()执行的任务不能返回结果

2 增加线程方法addWorker()

java 复制代码
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    // 获取线程池运行的状态
    for (int c = ctl.get(); ; ) {

        // 先做一个到底应不应该创建线程的判断
        if (runStateAtLeast(c, SHUTDOWN) && (runStateAtLeast(c, STOP) ||
                firstTask != null || workQueue.isEmpty()))
            return false;

        for (; ; ) {
            //获取并判断线程池中正在运行的线程数量是否符合要求。若core为true则判断是否达到核心线程数,若为false则判断是否达到最大线程数
            if (workerCountOf(c) >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                return false;
            // 新增线程数,如果成功,则跳出循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  //  // 再次获取线程池状态参数
            // 再次检查状态是否已到 SHUTDOWN 或更高:
            if (runStateAtLeast(c, SHUTDOWN))
                // 状态发生变化,重新开始整体判断
                continue retry;
            // 否则 CAS 因 workerCount 更改而失败;重试内部循环
        }
    }

    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 {
                // // 再次获取线程池状态
                int c = ctl.get();

                // 先做一个是否可以运行线程的判断
                // 1.线程池状态处于运行状态
                // 2.线程池状态处于小于SHUTDOWN状态且firstTask==null,因为SHUTDOWN状态不接受新的任务
                if (isRunning(c) || (runStateLessThan(c, STOP) && firstTask == null)) {
                    //如果线程的状态不是"NEW",意味着:
                    //线程已经启动过,不能再次启动或需要特殊处理。
                    if (t.getState() != Thread.State.NEW)
                        throw new IllegalThreadStateException();
                    // 将worker添加到一个hashset中
                    workers.add(w);
                    // 做一个标志,表示worker线程添加到了hashset当中
                    workerAdded = true;
                    int s = workers.size();
                    if (s > largestPoolSize)
                        // 用于记录出现过的最大线程数。
                        largestPoolSize = s;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                // 执行线程,会调用Worker中的run()方法
                t.start();
                // 线程已经处于已经启动的状态
                workerStarted = true;
            }
        }
    } finally {
        // 线程启动失败,做一些回滚的操作
        if (!workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

3 Worker执行

Worker的run方法内部就是调用runWorker()方法

java 复制代码
final void runWorker(Worker w) {
    //获取当前线程
    Thread wt = Thread.currentThread();
    //获取任务
    Runnable task = w.firstTask;
    // 显式将任务置为空,防止产生错乱的问题,下一次拿到重复的
    w.firstTask = null;
    // 将线程state置为0(创建Worker的时候state为-1),运行线程时允许线程中断
    w.unlock(); // 释放Worker对象上之前可能持有的锁或同步状态。
    boolean completedAbruptly = true;
    try {
        // 循环判断任务(firstTask或从队列中获取的task)是否为空
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // 判断线程池的状态,是否至少处于stop状态
            if ((runStateAtLeast(ctl.get(), STOP) ||
                    // 线程池刚进入停止状态,且工作线程wt刚被清除中断标记
                    (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
                    // 工作线程wt尚未被中断
                    !wt.isInterrupted())
                wt.interrupt();
            try {
                // 回调,可以适当做扩展
                beforeExecute(wt, task);
                try {
                    // 执行任务
                    task.run();
                    afterExecute(task, null);
                } catch (Throwable ex) {
                    afterExecute(task, ex);
                    throw ex;
                }
            } finally {
                task = null;
                // 线程执行完成个数,起到一个统计的作用
                w.completedTasks++;
                w.unlock();
            }
        }
        // 当当前线程没有获取到需要执行的任务时,或执行任务时异常时执行processWorkerExit()
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

4 获取任务getTask()

java 复制代码
private Runnable getTask() {
    boolean timedOut = false; // 最后一个 poll()是否超时

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

        // 当线程池状态至少是SHUTDOWN时且满足以下两个条件之一时
        // 1.线程池状态至少是STOP时;
        // 2.队列是空的;
        if (runStateAtLeast(c, SHUTDOWN) && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
            //工作线程数wc减去1,然后直接返回null
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // allowCoreThreadTimeOut 是否允许核心工作线程空闲超时销毁,默认是false,可以设置为true
        // 工作线程数大于核心线程数
        // 满足两个条件之一,timed为true
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 此处对线程开始进行销毁减少
        if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
            // 减少线程数量
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 如果timed为true,表示允许核心线程空闲超时被销毁或当前线程数大于核心线程数,通过poll()方法做超时拉取,keepAliveTime时间内没有等待到有效的任务,则返回null
            // 如果timed为false,表示不允许核心线程空闲超时被销毁且当前线程数小于等于核心线程数,通过take()做阻塞拉取,会阻塞到有下一个有效的任务时候再返回(一般不会是null)
            Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
            if (r != null)
                return r;
            // 取不到任务的时候timedOut = true
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

现在来着重分析一下getTask()中的几个判断,是对空闲线程进行销毁的一个重要依据

java 复制代码
if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty()))

(wc > maximumPoolSize || (timed && timedOut)):

wc > maximumPoolSize表示当前线程数大于最大线程数,timed为true表示核心线程允许被空闲超时销毁或者线程池中的线程数大于核心线程,timedOut为true表示该线程上一次的循环拉取任务超时了。让我们想想正常情况下为什么拉取任务会超时,那就是任务队列中没有任务了。该表达式为真时表示线程池中的线程数大于最大线程数 或者当前剩余的任务多于我们所需要的线程数

wc > 1 || workQueue.isEmpty()): 该表达式为真时表示当前至少有一个存活的线程 或者任务队列为空

综上,如果当前线程数大于最大核心线程数,或者在没有任务时多于我们所需要保留的线程数为真,那么多余的线程我们就需要进行销毁。返回为null,根据runWorker()源码,我们知道当获取的任务为空时,会跳出循环调用processWorkerExit()进行线程销毁。

java 复制代码
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();

根据当前的线程数量和核心线程空闲策略还有任务判断拉取任务方式,使用哪种方式拉取可以看上面注释。 当poll()超时时会返回null,当r为null时timedOut为true,然后进行下一次循环。在下一次循环时根据当前线程池条件判断是否返回null,若返回null则进行线程销毁。

5 线程池的销毁善后工作

java 复制代码
// 处理一个工作线程(Worker)结束或退出时的相关操作
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // 1. 处理 workerCount 计数
    if (completedAbruptly) // 如果线程因异常退出(如任务抛出未捕获异常)
        decrementWorkerCount();     //减少workerCount

    // 2. 更新线程池状态(加锁保证线程安全)
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        // 从Worker集合中删除该worker
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    // 3. 尝试终止线程池(如所有线程退出且队列为空)
    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {    // 线程池未停止(RUNNING 或 SHUTDOWN)
        if (!completedAbruptly) {   // 正常退出(如任务执行完毕)
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;     // 计算最小需要维持的线程数
            if (min == 0 && !workQueue.isEmpty())   // 允许核心线程超时且队列非空
                min = 1;     // 至少保留 1 个线程处理队列任务
            if (workerCountOf(c) >= min)    // 当前线程数足够,无需补充
                return; // replacement not needed
        }
        // 补充新线程(异常退出或线程数不足)
        addWorker(null, false); // 创建不绑定初始任务的线程
    }
}

以上就是线程池执行流程的核心源码部分,还有一些其他操作的源码在此处就不一一列举了。

相关推荐
ak啊37 分钟前
域名系统(DNS)
运维·前端·后端
neoooo1 小时前
MySQL中INT数据类型的括号、ZEROFILL和UNSIGNED关键字大揭秘!
后端·mysql·面试
neoooo1 小时前
Java与MySQL并发控制的共通思想:深入剖析锁机制与比较并交换
java·后端·mysql
李明卫杭州1 小时前
架构设计中的4+1视图
后端·架构
随缘而动,随遇而安1 小时前
第七十篇 从餐厅后厨到电影院选座:生活场景拆解Java并发编程核心
java·大数据·后端·生活
三两肉1 小时前
Spring Boot 启动流程深度解析:从源码到实践
java·spring boot·后端·启动流程
love530love1 小时前
【笔记】解决虚拟环境中找不到 chromedriver 的问题
前端·人工智能·笔记·后端·python
前端付豪1 小时前
网易微前端架构实战:如何管理100+子应用而不崩
前端·后端·架构
魔镜魔镜_谁是世界上最漂亮的小仙女1 小时前
java基础知识【java核心类】
java·后端
lihainuo1 小时前
一篇学会Spring Boot Starter封装:高可用JWT组件从配置到实战
后端