【Java 线程池】【五】线程池的预热、关闭、其它统计类方法原理

1  前言

上一节我们看了ThreadPoolExecutor线程池的核心参数、怎么通过这些参数构建和设计线程池的、向线程池提交任务的execute方法内部流程、线程池内部Worker工作者的原理,今天就来看一下ThreadPoolExecutor线程池一些其它方法,比如线程池的预热、关闭、线程池的一些统计类方法等等。

2  线程池的预热

我们先来看下线程池的预热方式:

2.1  preStartCoreThread 预热:创建出一个核心线程

public boolean prestartCoreThread() {
    // 获取当前线程数
    // 如果 当前线程数 < corePoolSize,则创建出来一个线程
    // 这里调用addWorker(null, true)方法,之前源码讲解过,
    // 传递true参数的时候,只有线程数小于corePoolSize的时候才会创建Worker出来
    return workerCountOf(ctl.get()) < corePoolSize &&
        addWorker(null, true);
}

2.2  preStartAllCoreThread 预热:提前创建出所有核心线程

public int prestartAllCoreThreads() {
    int n = 0;
    // 这里调用addWorker(null,true)
    // true表示 当前线程 < corePoolSize的时候允许创建线程
    // 所以这里的while会一直创建出Worker,直到 线程数 == corePoolSize位置
    while (addWorker(null, true))
        ++n;
    return n;
}

预热方法都很简单,这里线程池提供的预热方法,主要是为了提前创建出来一些线程,提前应对大批量的任务情况。

3  线程池的关闭

3.1  shutdown 优雅关闭线程池

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    // 进行加锁
    mainLock.lock();
    try {
        // 做一些安全检查,不是核心点
        checkShutdownAccess();
        // 把当前线程池的状态改为SHUTDOWN
        advanceRunState(SHUTDOWN);
        // 销毁线程池中空闲的线程
        interruptIdleWorkers();
        // 关闭线程池的回调钩子,可以做一些自定义操作
        onShutdown();
    } finally {
        mainLock.unlock();
    }
    // 尝试进行中止线程池
    tryTerminate();
}

3.1.1  interruptIdleWorkers 方法

我们上节看过interruptIdWorkers方法,我们这回顾一下:

private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}
// 这里传入的onlyOne参数,表示是否只销毁一个空闲的线程
// true:如果线程池有多个空闲线程,只会销毁一个就退出,比如有10个空闲线程,只销毁1个
// false:销毁所有空闲的线程
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    // 进行加锁
    mainLock.lock();
    try {
        // 遍历worker容器
        for (Worker w : workers) {
            Thread t = w.thread;
            // 首先判断线程t是非中断状态才能中断,如果中断了那么就不需要再中断了
            // 然后尝试去获取worker的锁,执行truLock方法,获取锁
            // 为什么说是优雅关闭,就是这里需要获取锁才能中断,而不是强制中断
            // 因为如果获取锁失败,说明当前worker正在执行任务,不能强制中断worker执行的任务,
            // 而是等它在非执行任务的时候才能中断
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    // 只有获取worker的互斥锁才能走到这里
                    // 这里就是中断线程
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            // 这里就是判断是否只是中断一个了,如果是就直接返回了,不是的话继续for循环中断其它空闲worker
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

上面的核心点其它就是:
(1)循环遍历worker,找出可以中断的worker,进行中断
(2)中断worker之前需要对worker进行加锁,只有获取锁成功,才说明worker不是在执行任务,才能中断。
(3)这里就是为什么shutdown是线程池提供的一种优雅关闭的方式,因为不会强行中断正在执行任务的worker

3.1.2  tryTerminate 方法

我们继续,看tryTerminated()方法,这个方法就是尝试中止线程池:

final void tryTerminate() {
    // 进行循环重试
    for (;;) {
        // 获取当前线程池的控制变量(包含线程池状态、线程数量)
        int c = ctl.get();
        // 这里直接return的情况,也就是不能进行中止线程池的情况有:
        // 1. isRunning(c) 也就是当前线程池还是RUNNING正常运行,不能中止
        // 2. 线程池为SHUTDOWN 并且 阻塞队列非空,说明还有一些任务没有执行,需要继续执行任务,不能中止
        // 3. runStateAtLeast(c,TIDYING) 至少是TIDYING,也就是可能是TIDYING、TERMINATED这两种状态之一
        // 处于TIDYING、TERMINATED 状态说明线程池已经关闭完成了,正在中止或者已经中止,这里就不需要再次中止了
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        // 如果当前当前线程数量不为0,不能中止线程池,需要中断所有线程才能中止线程池
        if (workerCountOf(c) != 0) {
            // 去中断线程池里面一个空闲的线程
            interruptIdleWorkers(ONLY_ONE);
            return;
        }
        // 走到这里说明上面的那些校验统统不满足
        // 也就是当前线程池的状态为SHUTDOWN或者STOP、阻塞队列为空、线程池线程数量为0,
        // 这个时候就可以尝试中止线程池了
        final ReentrantLock mainLock = this.mainLock;
        // 加锁
        mainLock.lock();
        try {
            // 设置线程池的状态为TIDYING(终止中..)
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    // 这里是终止的一个回调钩子方法,可以做一些自定义操作
                    terminated();
                } finally {
                    // 设置当前线程池状态为TERMINATEd(已终止)
                    ctl.set(ctlOf(TERMINATED, 0));
                    // 唤醒因为termination条件而沉睡的线程
                    termination.signalAll();
                }
                return;
            }
        } finally {
            // 释放锁
            mainLock.unlock();
        }
    }
}

shutdown方法内部的流程都看完了,我们画个图来理解一下:

3.2  shutdownNow 暴力关闭线程池

上面的shutdown优雅关闭线程池的方式看完,现在再来讲一种暴力的关闭线程池的方式。
ThreadPoolExecutor提供了另外一个关闭线程池的方法叫做shutdownNow(),听名字就知道是立即关闭线程池了。
这种方式会立即关闭线程池,立即中断正在运行的线程,强行清空阻塞队列里面还未运行的任务,销毁所有线程,然后终止掉线程池,是一种比较暴力的方式。

public List<Runnable> shutdownNow() {
    // 这里是还未运行的任务,也就是阻塞队列里面的任务
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    // 加锁
    mainLock.lock();
    try {
        // 做一些安全检查,非核心重点
        checkShutdownAccess();
        // 这里就是核心点了,直接设置线程池状态为STOP
        advanceRunState(STOP);
        // 这里强制中断所有线程
        interruptWorkers();
        // 这里就是获取阻塞队列的所有任务,放入tasks列表中
        // 同时清空阻塞队列
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    // 这里是尝试终止线程池,上面讲解shutdown的时候已经分析过源码了
    tryTerminate();
    return tasks;
}

3.2.1  interrutpWorkers 方法

我们接下来看看interrutpWorkers方法,是怎么强行中断所有线程的:

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 这里核心点就是遍历worker容器,调用每个worker的interruptIfStarted方法
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

再来看看Worker的interruptIfStarted方法:

void interruptIfStarted() {
    Thread t;
    // getState >=0 说明初始化了
    // t != null说明worker内部的线程存在
    // !isInterrupted说明未被中断过,可以进行中断
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
        try {
            // 直接执行中断,不管线程在干啥,直接中断
            t.interrupt();
        } catch (SecurityException ignore) {
        }
    }
}

(1)看这里的interruptIfStarted方法就比较暴力了,直接中断运行的线程,不管线程是否在运行任务。
(2)对比之前shutdown优雅关闭线程池的时候,中断线程需要获取锁tryLock,如果获取成功说明线程未在执行任务,可以进行中断。而这里shutdownNow() 不管这个,直接停掉。

4  线程池的其它统计方法

4.1  getPoolSize 获取线程池的大小

public int getPoolSize() {
    final ReentrantLock mainLock = this.mainLock;
    // 进行加锁
    mainLock.lock();
    try {
        // 如果当前线程池状态至少是TIDYING,也就是处于TIDYING、TERMINATED中的一种
        // 说明线程池已经终止了,当前线程个数为0
        // 如果处于RUUNING、SHUTDOWN、STOP状态,返回worker容器中worker工作者个数
        return runStateAtLeast(ctl.get(), TIDYING) ? 0
            : workers.size();
    } finally {
        mainLock.unlock();
    }
}

4.2  getActiveCount 正在执行任务的线程个数

public int getActiveCount() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        int n = 0;
        for (Worker w : workers)
            // isLock方法说明被加锁了,正在执行任务
            // 所以也就是统计正在执行task任务的线程个数
            if (w.isLocked())
                ++n;
        return n;
    } finally {
        mainLock.unlock();
    }
}

4.3  getCompletedTaskCount 线程池完成的任务总数

public long getCompletedTaskCount() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        long n = completedTaskCount;
        for (Worker w : workers)
            // 这里就是将每个worker完成的任务数进行加和
            // 就得到已经完成的任务总数了
            n += w.completedTasks;
        return n;
    } finally {
        mainLock.unlock();
    }
}

4.4  getTaskCount 提交到线程池的任务总数

public long getTaskCount() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        long n = completedTaskCount;
        for (Worker w : workers) {
            // 统计已经完成的任务总数
            n += w.completedTasks;
            if (w.isLocked())
                // 正在执行的任务也要算进去
                ++n;
        }
        // 阻塞队列的任务也要算进入
        return n + workQueue.size();
    } finally {
        mainLock.unlock();
    }
}

4.5  getLargestPoolSize 线程池线程数达到的最大值

public int getLargestPoolSize() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // largetstPoolSize记录着这个线程池曾经线程数量的最大值,直接返回即可
        return largestPoolSize;
    } finally {
        mainLock.unlock();
    }
}

5  小结

好了,到这里我们看了线程池的预热;线程池的关闭方式,包括优雅关闭和暴力关闭、线程池的一些任务统计、线程数量统计等等,有理解不对的地方欢迎指正哈。

posted @ 2023-04-12 06:46  酷酷-  阅读(701)  评论(1编辑  收藏  举报