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