源码赏析:Java线程池中的那些细节

我们常说非核心线程空闲超时之后就会被销毁,线程池又是如何判断线程是否空闲的呢?在调用了shutdown()或者shutdownNow()线程池内部又是如何变化的,将线程池进行逐步关闭的呢?

1、shutdown()关闭流程分析

假设此时线程池正常运行,核心线程为5,最大线程数为10。此时达到最大线程数,任务队列中还有10个任务

java 复制代码
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;    // 获取线程池的主锁
    mainLock.lock();    // 加锁(保证原子性)
    try {
        checkShutdownAccess();    // 权限检查
        advanceRunState(SHUTDOWN);    // 推进状态到 SHUTDOWN
        interruptIdleWorkers();    // 中断空闲工作线程
        onShutdown(); 
    } finally {
        mainLock.unlock();
    }
    tryTerminate();    // 尝试终止线程池
}

源码如上所示,设置线程池状态然后中断空闲线程。最后尝试关闭线程池。 interruptIdleWorkers()会调用此方法,传入参数为false

java 复制代码
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 遍历所有线程
        for (Worker w : workers) {
            Thread t = w.thread;
            // 若线程没有被中断或尝试获取锁成功
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    // 将线程设为中断
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            // 若只需中断一个线程进行返回
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

循环中的w.tryLocak若为真,代表此线程此时没有执行任务处于空闲状态。因为线程池在执行任务时会进行加锁(详细流程可以看我前面那篇将线程池工作流程的文章

尝试关闭线程池的tryTerminate()源码如下

java 复制代码
final void tryTerminate() {
    for (; ; ) {
        int c = ctl.get();
        //当处在以下三种状态时方法提前返回
        //1.线程池还在运行
        //2.状态已经达到或超过TIDYING
        //3.未达到STOP状态且任务队列非空,还存在未处理的任务
        if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateLessThan(c, STOP) && !workQueue.isEmpty()))
            return;

        //仍然有活跃线程
        if (workerCountOf(c) != 0) { // Eligible to terminate
            //仅中断一个线程
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // CAS 更新状态为 TIDYING
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    // 执行终止钩子
                    terminated();
                } finally {
                    // 更新为 TERMINATED
                    ctl.set(ctlOf(TERMINATED, 0));
                    // 通知等待线程(如 awaitTermination())
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
    }
}

若当前线程池还有线程,则中断一个空闲线程后返回。若没有线程则将线程池设为terminated关闭状态。

在上面所说的情况下,只会对正在获取任务的线程进行中断。线程池中的线程都处于正在执行任务或获取任务。如果提交的任务中没有设置响应中断,那么只会在获取任务的过程中进行响应中断,因为线程池的runwork()中并没有获取中断响应。

线程池中的线程获取任务的部分源码如下所示

java 复制代码
try {
    Runnable r = timed ?
            workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
            workQueue.take();
    if (r != null)
        return r;
    // 取不到任务的时候timedOut = true
    timedOut = true;
} catch (InterruptedException retry) {
    timedOut = false;
}

poll()take()会响应中断抛出异常并清除中断状态,然后被捕获,将timeOut设为false,再次进行下一次任务获取。所以即使调用了shutdown()方法仍然会处理已提交的任务,直到线程池中的任务对列为空,对线程进行销毁操作。 获取任务中还有这样一段代码

java 复制代码
// 当前状态大于等于SHUTDOWN且(大于等于STOP状态或者任务队列为空)
if (runStateAtLeast(c, SHUTDOWN) && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
    //工作线程数wc减去1,然后直接返回null
    decrementWorkerCount();
    return null;  // 获取到的任务为Null,进行销毁处理
}

在返回为null之后runworker()会跳出循环,执行processWorkerExit(w, completedAbruptly);在processWorkerExit会再次调用tryTerminate() 方法对线程状态进行设置为关闭状态。所以对于我们一开始所说的那种情况下的shutdown()关闭流程为:

  1. 所有线程在获取任务时被中断,然后在poll()时被清除中断重新进行下一次任务的获取,直到任务队列为0。
  2. 当任务队列的最后一个任务被取走时为空,此时再有线程执行完任务再取获取时,减少线程池中的线程个数并返回Null
  3. 返回null进行线程销毁,并调用tryTerminate()尝试关闭线程池
  4. 直到最后一个线程在获取任务时返回null,会在tryTerminate()将线程池设为关闭状态。至此线程池完全关闭。

2、shutdownNow()关闭流程分析

java 复制代码
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;    // 存储未执行的任务
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();    // 权限检查
        advanceRunState(STOP);    // 推进状态到 STOP
        interruptWorkers();     // 中断所有工作线程
        tasks = drainQueue();    // 排空任务队列并返回未执行任务
    } finally {
        mainLock.unlock();
    }
    tryTerminate();     // 尝试终止线程池
    return tasks;    // 返回未执行的任务列表
}

会将所有线程设为中断,然后排空任务队列。这样线程池就获取不到任务了,只会执行当前正在执行的任务。同样的,如果提交的任务没有响应中断,那么线程也只会在获取任务的poll方法时响应中断。 假设线程池处于最大线程数10,且都在执行任务,任务队列有20个任务,此时执行shutdownNow(),关闭流程如下:

  1. 将线程池设为STOP状态,将所有线程都设为中断状态
  2. 排空任务队列
  3. 尝试执行关闭线程池,但因为此时线程池的正在执行的任务还没有执行完,不会进行关闭。
  4. 各个线程的任务执行完后会再次获取任务,但判断到当前处于STOP状态直接减少线程个数并返回null
  5. 执行线程销毁操作,每次销毁都会超时关闭线程池
  6. 当最后一个线程进行销毁时,会将线程池设为关闭状态
相关推荐
朝新_3 小时前
【多线程初阶】阻塞队列 & 生产者消费者模型
java·开发语言·javaee
立莹Sir3 小时前
Calendar类日期设置进位问题
java·开发语言
季鸢4 小时前
Java设计模式之状态模式详解
java·设计模式·状态模式
@yanyu6665 小时前
springboot实现查询学生
java·spring boot·后端
ascarl20105 小时前
准确--k8s cgroup问题排查
java·开发语言
magic 2455 小时前
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
java
爱敲代码的憨仔5 小时前
分布式协同自动化办公系统-工作流引擎-流程设计
java·flowable·oa
纪元A梦6 小时前
分布式拜占庭容错算法——PBFT算法深度解析
java·分布式·算法
卿着飞翔6 小时前
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
java·rabbitmq·java-rabbitmq
陈阿土i6 小时前
SpringAI 1.0.0 正式版——利用Redis存储会话(ChatMemory)
java·redis·ai·springai