核心线程,非核心线程的区别你还记得吗?

news/2024/11/24 7:30:11/


/   今日科技快讯   /

近日,腾讯发布了2020年第三季度业绩报告。财报显示,腾讯第三季度营收1254.5亿元,市场预估1238.29亿元,去年同期972.4亿元,同比增长29%。净利润385.4亿元,同比增长89%,市场预期308.1亿元,去年同期203.82亿元。

/   作者简介   /

一晃又到了周五啦,祝大家周末愉快哦!我们下周见!

本篇文章来自小猪快跑22的投稿,通过实例带着大家一起来分析ThreadPoolExecutor源码,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章!

小猪快跑22的博客地址:

https://blog.csdn.net/zhujiangtaotaise

/   前言   /

其实大概1年前就想把线程池的源码完整的撸一遍了,但是看的时候太注重细节了,比如其中的CAS操作、AQS以及ReentrantLock的lock、tryLock等,结果就是跑偏了;所幸今年年初的时候有时间,就把AQS的源码、CAS的使用以及ReentrantLock等一些并发编程的源码撸了一遍,再来看线程池的源码,感觉还是非常的舒爽,哈哈哈。共勉。

讲线程池的原理之前,先得了解一下线程池中几个重要的概念。

  1. 核心线程数 (corePoolSize):核心线程的数量;它的作用可以这样理解:向线程池中添加任务,如果线程池中的线程数量小于 corePoolSize,那么直接新建线程执行任务;如果线程池中的线程数量大于corePoolSize,那么就会往 阻塞队列workQueue中添加任务,此时如果阻塞队列满了且线程池中的线程数量小于最大线程数 maximumPoolSize,那么也会新建一个线程执行任务;如果阻塞队列满且线程数量大于最大线程数maximumPoolSize,那么会执行饱和策略,默认的策略是抛弃要加入的任务。

  2. 最大线程数 (maximumPoolSize):如果阻塞队列满了,则判断线程池中的线程数量是否小于 maximumPoolSize,是则直接新建一个线程来处理任务,否则执行饱和策略。

  3. 阻塞队列**(workQueue)**:线程池中的线程数量大于核心线程的数量,则将新建的任务加入到阻塞队列。

  4. 空闲线程的存活时间 (keepAliveTime):线程空闲下来之后,线程的存活时间,超过这个时间还没有任务执行,则结束该线程。注意,这个回收只是回收非核心线程,比方说核心线程数是2,最大线程数是6,假设任务非常多,最后创建了6个线程来执行任务,最后后回收4个非核心线程,而核心线程不会回收,除非你任务设置要回收核心线程。

  5. 饱和策略 (RejectedExecutionHandler):当等待队列已满,线程数也达到最大线程数时,线程池会根据饱和策略来执行后续操作,默认的策略是抛弃要加入的任务。

下面来一张图来说明下:

线程池的几种状态

  1. RUNNING: 运行状态,能够接受新的任务且会处理阻塞队列中的任务。

  2. SHUTDOWN:关闭状态,不接受新任务,但是会处理阻塞队列中的任务,执行线程池的 shutDown()对应的就是此状态。

  3. STOP:停止状态,不接受新的任务,也不会处理等待队列中的任务并且会中断正在执行的任务。调用线程池的 shutDownNow()对应的是此状态

  4. TIDYING: 整理,即所有的任务都停止了,线程池中线程数量等于0,会调用 terminated()如果你自己实现线程池的话。

  5. TERMINATED:结束状态,terminated()方法执行完了。

下面是一些重要的变量注释:

 //CAS, 它的高三位表示线程池的状态,低29位表示线程池中现有的线程数private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));//表示线程池线程数的bit数private static final int COUNT_BITS = Integer.SIZE - 3;//最大的线程数量,数量是完全够用了 0001 1111 1111 1111 1111 1111 1111 1111private static final int CAPACITY   = (1 << COUNT_BITS) - 1;// runState is stored in the high-order bits//1110 0000 0000 0000 0000 0000 0000 0000private static final int RUNNING    = -1 << COUNT_BITS;//0000 0000 0000 0000 0000 0000 0000 0000private static final int SHUTDOWN   =  0 << COUNT_BITS;//0010 0000 0000 0000 0000 0000 0000 0000private static final int STOP       =  1 << COUNT_BITS;//0100 0000 0000 0000 0000 0000 0000 0000private static final int TIDYING    =  2 << COUNT_BITS;//0110 0000 0000 0000 0000 0000 0000 0000private static final int TERMINATED =  3 << COUNT_BITS;// Packing and unpacking ctl//获取线程池的状态private static int runStateOf(int c)     { return c & ~CAPACITY; }//获取线程池中已创建线程的数量private static int workerCountOf(int c)  { return c & CAPACITY; }//组装状态和数量,成为ctlprivate static int ctlOf(int rs, int wc) { return rs | wc; }private static boolean runStateLessThan(int c, int s) {return c < s;}private static boolean runStateAtLeast(int c, int s) {return c >= s;}//判断线程是否在运行private static boolean isRunning(int c) {return c < SHUTDOWN;}

线程池的添加任务的源码解析

我这里为了能将的清楚,我将线程池的主要代码都Copy了,加了些注释,其他的没变。

为了说明例子,我这里的线程池定义如下:

 ExecutorService executorService = new ThreadPoolExecutor(1, 2, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1));

其中 核心线程数等于1,最大线程数等于2,空闲线程的存活时间为100毫秒,阻塞队列是大小为1的数组类型的队列,此队列只能存放一个任务且不会自动扩容。

下面我new了3个任务放进线程池,如下:

    Runnable runnable1 = () -> {Log.e("test thread pool", "r1 start");try {Thread.sleep(3_000);} catch (InterruptedException e) {e.printStackTrace();}Log.e("test thread pool", "r1 end");};Runnable runnable2 = () -> {Log.e("test thread pool", "r2 start");try {Thread.sleep(3_000);} catch (InterruptedException e) {e.printStackTrace();}Log.e("test thread pool", "r2 end");};Runnable runnable3 = () -> {Log.e("test thread pool", "r3 start");try {Thread.sleep(3_000);} catch (InterruptedException e) {e.printStackTrace();}Log.e("test thread pool", "r3 end");};// 直线线程池的添加任务:executorService.execute(runnable1);executorService.execute(runnable2);executorService.execute(runnable3);

按照上面讲的线程池原理那么执行的流程应该是这样:

先创建一个线程来执行Runnable1,此时核心线程数等于1,那么会把Runnable2放到阻塞队列中去,由于阻塞对列只能存放1个任务,且最大线程数等于2,那么会新建一个线程来执行Runnable3。

注意,我这里的3个任务我都sleep(3_000),我为什么这里要让你们注意这里呢?

因为,如果我的任务1的任务很简单就是打印一行日志的话,那么这个任务很快就会执行完,那么可能在执行任务3的时候,任务1已经执行完,那么执行任务1的线程就会去阻塞队列中将任务2出队且执行,那么任务3就会被加入到阻塞队列中。

execute 方法:

  public void execute(Runnable command) {if (command == null)throw new NullPointerException();       int c = ctl.get();int wtc = workerCountOf(c); // 计算线程池中当前线程的数量Log.e("test thread pool", "c = " + c + ", wtc = " + wtc);// 如果线程的数量小于核心线程数,那么直接提交任务,并且创建线程来执行任务if (wtc < corePoolSize) {if (addWorker(command, true))return;// 如果提交任务失败,可能是线程池执行了shunDown或shutDownNow操作,// 那么重新获取ctl的值,执行下面的流程c = ctl.get();Log.e("test thread pool", "addWorker failed c = " + c);}// 如果线程池是运行状态,那么将任务添加到阻塞队列,执行到这里只有2个条件:// 条件1:核心线程数已满// 条件2:线程池执行了shunDown或shutDownNow操作if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();Log.e("test thread pool", "recheck = " + recheck);// 如果当前线程池不是运行状态,那么将任务从阻塞队列中移除并执行拒绝策略if (!isRunning(recheck) && remove(command)) {Log.e("test thread pool", "...reject...");reject(command);} else if (workerCountOf(recheck) == 0) { // 如果线程池中线程数量等于0,那么就添加一个空任务,目的就是继续执行阻塞队列中的任务Log.e("test thread pool", "...addWorker a null task...");addWorker(null, false);}}// 如果核心线程数已满且阻塞队列已满,那么就开启一个新线程来执行任务,// 如果添加失败则执行抛弃策略// 这里面的失败的条件,一般是执行下面addWorker(command, false)的时候,// 另外一个线程执行了线程池的shutDown()操作,这种情况基本不会出现,//因为线程池的操作如extcute或shutDown一般都是主线程中的,// 所以 addWorker和shutdown都是顺序执行的,不会出现失败的情况。else if (!addWorker(command, false)) { Log.e("test thread pool", "...reject..2....");reject(command);}}

按照之前自定义的线程池executorService = new ThreadPoolExecutor(1, 2, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1))执行的结果如下:

只看我用绿框框出来的即可,其他log是我跟踪分析的时候用的。

结果分析:

开始时因为线程池中线程个数是0,而且核心线程数是1,所以直接创建一个线程来执行任务1;

添加任务2的时候,此时线程池中线程的个数等于1了,即核心线程已满,所以将任务2添加到阻塞队列中;

添加任务3的时候,核心线程已满且阻塞队列已满(这里我队列的大小设置为1,即只能存放1个任务),但是线程池中的线程数小于最大的线程数(2个),所以会新建一个线程执行任务3。从上面的日志可以看出,执行任务1和任务3的线程分别是 :pool-1-thread-1 和 pool-1-thread-2。

日志还可以看出,执行任务1和任务2的线程都是 pool-1-thread-1,这里面涉及到的就是线程池中线程的复用,到底是怎么实现的呢?

那就得看看 之前的addWorker方法啦:

 private boolean addWorker(Runnable firstTask, boolean core) {retry:for (; ; ) {int c = ctl.get();int rs = runStateOf(c); // 获取线程池的状态Log.e("test thread pool", "addWorker rs = " + rs + ", firstTask = " + firstTask + ", ....... core = " + core);//如果线程池的状态到了SHUTDOWN或者之上的状态时候,只有一种情况还需要继续添加线程,//那就是线程池已经SHUTDOWN,但是队列中还有任务在排队,而且不接受新任务(所以firstTask必须为null)//这里还继续添加线程的目的是,尽快完成阻塞队列中的任务if (rs >= SHUTDOWN &&!(rs == SHUTDOWN &&firstTask == null &&!workQueue.isEmpty()))return false;for (; ; ) {// 获取线程个数int wc = workerCountOf(c);// 如果线程数大于CAPACITY 或者线程数大于等于核心线程数或者最大线程数// 表示添加任务失败if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;// 线程池中线程的个数加1,如果成功的话直接跳出最外层的for循环    if (compareAndIncrementWorkerCount(c))break retry;// 检测当前线程状态如果发生了变化,则继续回到retry,重新开始循环c = ctl.get();  // Re-read ctlif (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类包装任务,真正的执行任务就是在Worker中w = new Worker(firstTask);final Thread t = w.thread;if (t != null) {final ReentrantLock mainLock = this.mainLock;mainLock.lock(); // 加锁try {int rs = runStateOf(ctl.get()); // 获取线程池的状态// rs < SHUTDOWN 表示线程池是运行状态// (rs == SHUTDOWN && firstTask == null) 表示线程池执行了shutDown,// 且阻塞队列中还有任务,这时候需要添加一个空的任务,即创建新的线程来加速阻塞队列中的任务尽快完成if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {if (t.isAlive()) // precheck that t is startablethrow new IllegalThreadStateException();workers.add(w);int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}if (workerAdded) {Log.e("test thread pool", "addWorker start");// 这里真正的在线程中处理任务啦t.start();workerStarted = true;}}} finally {// 添加任务出错的机制if (!workerStarted)addWorkerFailed(w);}return workerStarted;}

addWorker的代码注释的很清楚,代码也比较简单,下面主要看看里面真正的任务执行类 Worker,和 添加任务失败的方法 addWorkerFailed。

真正的任务执行类 Worker

Worker的代码很简单,主要就是继承了AQS来加、解锁,以及创建线程来执行任务,构造函数如下:

 Worker(Runnable firstTask) {setState(-1); // 这里面就是设置AQS中state的值为-1,用途是调用shutDown时根据状态来响应中断操作的,// 要执行的 Runnable任务this.firstTask = firstTask;// 通过线程工厂方法来创建新的线程this.thread = getThreadFactory().newThread(this);}

真正执行的任务方法runWorker:

  final void runWorker(Worker w) {Log.e("test thread pool", "runWorker begin task = " + w.firstTask);Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // allow interruptsboolean completedAbruptly = true;try {// 如果task你为空,或者去阻塞队列中去取任务不为空,这里的getTask 如果阻塞队列中任务为空 会阻塞当前线程// 这里就是线程复用的核心,比方说当这个程执行完当前任务后,就去队列中取任务来执行,这就完成了线程的复用while (task != null || (task = getTask()) != null) {Log.e("test thread pool", "runWorker  while task = " + task);w.lock();// 第一个条件只要调用了shutDownNow才会成立,如果调用了shutDownNow 那么就会执行线程的中断即中断正在执行的任务if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted()) {Log.e("test thread pool", "runWorker  interrupt");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 {Log.e("test thread pool", "runWorker  processWorkerExit");// 处理线程完成所有任务后的操作,注意,这里可能执行不到,为什么?// 因为上面注释说了,getTask取队列中的任务时,// 如果队列为空,那么就会阻塞住当前线程,所有这里就执行不到了;// 但是调用shutDown就能执行到这里了,这也是为什么我们经常看到线程池的例子都是excute(r)后调用shutDown()processWorkerExit(w, completedAbruptly);}}

下面再看看getTask方法

getTask方法 就是从阻塞队列中获取待执行的任务,按照先进先出的原则取任务。

private Runnable getTask() {boolean timedOut = false; // Did the last poll() time out?for (; ; ) {int c = ctl.get();int rs = runStateOf(c); // 获取线程池状态Log.e("test thread pool", "--getTask  ----- c = " + c + ", rs = " + rs);// 线程池的状态是大于等于 SHUTDOWN 且 状态大于等于STOP或者阻塞队列为空// 这里满足的条件有2种:// 1. 调用shutDown 后直到阻塞队列中的任务都执行完// 2. 调用 shutDownNow() 后线程池的状态就变成STOP了if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { // **注1**decrementWorkerCount(); // 线程池中的线程数减1Log.e("test thread pool", "--getTask  c = " + ctl.get());// return null后,runWorker中while循环获取任务就会结束,就会执行 // runWorker 中的processWorkerExit方法,return null;}int wc = workerCountOf(c); // 获取线程池中线程数// 一般没设置核心线程的空闲存活时间,allowCoreThreadTimeOut 就为false// wc > corePoolSize 表示阻塞队列满了后,// 且线程数没达到maximumPoolSize ,就会新建线程执行任务boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;// **注2**// wc > maximumPoolSize 满足的就是 但线程池已经执行了shutDown,// 但是阻塞队列中任务还没有执行完,所以会通过 addWorker(null, false)// 创新新线程来加速出来队列中的任务if ((wc > maximumPoolSize || (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) { // **注3**// 满足条件就线程数减1,返回nullif (compareAndDecrementWorkerCount(c)) // **注4**return null;continue;}Log.e("test thread pool", "--getTask  -----timed = " + timed + ", wc = " + wc + ", c = " + c);try {// 创建非核心线程后,timed 一定为ture,// 下面的获取任务的2中方法稍后再来讲,这里也是为什么只回收非核心线程而不回收核心线程的核心代码Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take(); // **注5**if (r != null) return r;timedOut = true;// **注6**} catch (InterruptedException retry) {timedOut = false;}}}

下面看看workQueue.poll 和 workQueue.take() 方法,我这里队列用的是 ArrayBlockingQueue。

public E poll(long timeout, TimeUnit unit) throws InterruptedException {long nanos = unit.toNanos(timeout);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {// 如果队列中任务空了,那么就会通过ReentrantLock等待 timeout时间后唤醒// ReentrantLock 不了解的话,这里就类似于Object.wait(nanos)while (count == 0) { if (nanos <= 0L)return null;nanos = notEmpty.awaitNanos(nanos);}return dequeue(); // 这个方法就是取出任务,然后调用 signal唤醒存放任务的线程} finally {lock.unlock();}}
public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {// 如果队列空了,就会调用await阻塞住当前线程,这时候只有玩队列中添加任务时才会唤醒该线程while (count == 0)notEmpty.await();return dequeue();} finally {lock.unlock();}}

可以看出上面的2个方法区别就是,poll 会在阻塞规定时间后会唤醒线程,然后导致getTask = null,最终回收该线程。

这里重点分析下为什么非核心线程能够被回收而核心线程最后会阻塞(在不调用shutDown的情况下)?

把自定义的线程池再次copy出来 :executorService = new ThreadPoolExecutor(1, 2, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1)),每个任务都耗时3秒,见上面定义的Runnable;

  1. 首先会新建一个核心线程Thread1来执行任务1,然后将任务2放到队列中去;最后创建一个非核心线程Thread2来执行任务3。

  2. 由于任务1先开始执行,所以任务1肯定也先执行完;任务1执行完后,由runWorker中可知,会通过while循环调用 getTask 方法去阻塞队列中去取任务,即会取到任务2,然后核心线程Thread1接着去执行任务2。

  3. 此时,任务3执行完成了,也去阻塞队列中取任务,即调用getTask, 我在getTask方法中加了6条注释来帮助分析:

注1:由于 线程池没有调用 shutDown或者shutDownNow,所以条件不成立。

注2:由于创建了2个线程所以 wc= 2 ,大于 corePoolSize(等于1),所以 timed = true

注3:maximumPoolSize = 2,且局部变量 timedOut 默认等于false,所以条件不成立

注5:timed 等于true,那么就会执行poll方法,由于此时队列中已经没有任务了,所以Thread2会阻塞100毫秒后自动唤醒,然后 timeOut = true ;

然后再此循环,走到注释3:

此时:timed = true 且 timeOut = true,wc = 2,所以 条件成立,调用 compareAndDecrementWorkerCount 把线程池的线程数减1并返回null。getTask返回null,所以Thread2 就完全执行完了,即回收了非核心线程了。

4.Thread1执行完任务2后,同样的调用 getTask方法。

注1:由于 线程池没有调用 shutDown或者shutDownNow,所以条件不成立;

注2:由于Thread2在调用getTask方法的时候 线程数已经减1了,所以 wc = 1,所以 timed = false;

注3:也就不成立了,走到注5,

注5:由于 timed = false,所以调用的是队列的 take方法,由于队列空了,所以会阻塞这个核心线程。

上面分析的是没有调用shutDown时的线程回收,下面讲的是调用shutDown后的线程回收。调用的代码如下:

 executorService.execute(runnable1);executorService.execute(runnable2);executorService.execute(runnable3);executorService.shutdown();

shutDown 代码如下:

   public void shutdown() {Log.e("test thread pool", "................shutdown  ...... c = " + ctl.get());final ReentrantLock mainLock = this.mainLock;mainLock.lock(); // 加锁,保证同时只有一个线程调用 shutDown方法try {// 检验调用shutDown的合法性checkShutdownAccess();// 设置线程池的状态为SHUTDOWN,且设置 ctl的值advanceRunState(SHUTDOWN);// 尝试中断正在执行的线程interruptIdleWorkers();onShutdown(); // hook for ScheduledThreadPoolExecutor} finally {mainLock.unlock();}// 当线程池的状态为shutDown且队列为空 或者 线程池状态为STOP且队列为空,那么把状态置为TERMINATEDtryTerminate();}

shutDown中需要看3个方法:advanceRunState、interruptIdleWorkers和tryTerminate

看看其中的advanceRunState :

 private void advanceRunState(int targetState) {// assert targetState == SHUTDOWN || targetState == STOP;for (; ; ) {int c = ctl.get();int tct = workerCountOf(c); // 线程的个数int ctc = ctlOf(targetState, tct); // 线程的个数位运算或 targetState    // 判断 c 是否 >= targetState, 或 cas操作设置 ctl的值是否成功if (runStateAtLeast(c, targetState) || ctl.compareAndSet(c, ctc))break;}}

interruptIdleWorkers最终调用的是interruptIdleWorkers(false),如下:

  

private void interruptIdleWorkers(boolean onlyOne) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// 创建线程执行任务的时候会将 worker 添加的 HashSet workers,具体见addWorker方法for (Worker w : workers) {Thread t = w.thread;      // 如果线程没有阻塞,那么通过AQS尝试获取锁,// 因为runWorker的时候调用了 w.lock(),所以tryLock返回false,    // 注意tryLock 和lock不一样,tryLock不会阻塞当前线程,而try会阻塞  if (!t.isInterrupted() && w.tryLock()) {                  try {t.interrupt();} catch (SecurityException ignore) {} finally {w.unlock();}}if (onlyOne)break;}} finally {mainLock.unlock();}}

tryTerminate() 方法如下:

 final void tryTerminate() {for (; ; ) {int c = ctl.get();   // 如果线程池是运行状态、或者至少是TIDYING、或者是SHUTDOWN 且队列不为空,则跳出循环if (isRunning(c) ||runStateAtLeast(c, TIDYING) ||(runStateOf(c) == SHUTDOWN && !workQueue.isEmpty())) { // **注1**return;}    if (workerCountOf(c) != 0) { // 线程个数不等于0,尝试中断一个正在执行的任务,ONLY_ONE 是true;然后直接跳出循环 // **注2**  interruptIdleWorkers(ONLY_ONE);return;}final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// CAS 操作ctl值为TIDYING是否成功,即设置线程池的状态为TIDYING是否成功boolean flag = ctl.compareAndSet(c, ctlOf(TIDYING, 0));// **注3**if (flag) { // **注4**try {terminated();} finally {// 成功的话,设置 线程池的状态为TERMINATEDctl.set(ctlOf(TERMINATED, 0)); // **注5**termination.signalAll();}return;}} finally {mainLock.unlock();}// else retry on failed CAS}}

分析:

1.执行完上述代码时,核心线程Threa1正在执行任务1,任务2放到了阻塞队列中,非核心线程Thread2正在执行任务3。主线程中调用 shutDown()。

2.由于任务1和任务2耗时3秒,在shutDown中调用advanceRunState(SHUT_DOWN) 后,SHUT_DOWN的值等于0, 将ctl的值设置为线程的个数,即2个(核心线程一个,非核心线程一个),这个方法很简单。

然后调用interruptIdleWorkers,发现正在运行的任务都占用了锁,tryLock为false,所以不会掉线程的中断方法。然后看看 tryTerminate,我在这个方法中加了 注;

注1 :由于调用了shutDown后,线程池的状态就变成了SHUT_DOWN,且此时阻塞队列中保存了任务2,所以条件成立,直接return,那么主线程中调用shutDown的方法分析完了,得出2点重要的结论:

a. ctl 的值等于2,即线程池中线程的个数

b. 线程池的状态为 SHUT_DOWN

3.当核心线程Thread1,执行完任务1的时候,调用getTask()来获取任务2去执行,这里暂且不多说

4.当非核心线程Thread2执行完任务3的时候,也去执行 getTask来获取任务,现在来分析 getTask中的代码,之前我在getTask中也添加了注1~注6。

注1:由于此时的线程池状态为 SHUTDOWN, 即 rs = SHUTDOWN,且workQueue是empty,所以条件if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty()))成立,执行 decrementWorkerCount()将线程池的线程减1,所以此时线程池中线程的个数为1,然后 return null,即 runWorker方法中的while循环while (task != null || (task = getTask()) != null)结束。

接着会调用 processWorkerExit(w, completedAbruptly),方法如下:

private void processWorkerExit(Worker w, boolean completedAbruptly) {if (completedAbruptly) // 正常为false,不用管decrementWorkerCount();final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {completedTaskCount += w.completedTasks;workers.remove(w);// 从works中删除已完成的Worker} finally {mainLock.unlock();}tryTerminate(); // **注1**int c = ctl.get();// 线程池的状态小于STOPif (runStateLessThan(c, STOP)) { // **注2**if (!completedAbruptly) {int min = allowCoreThreadTimeOut ? 0 : corePoolSize;if (min == 0 && !workQueue.isEmpty())min = 1;if (workerCountOf(c) >= min)return; // replacement not needed}addWorker(null, false);}}
这里就不带着分析**processWorkerExit**了。下面再带着分析

5.此时核心线程Thread1执行完任务2,会再次调用 getTask方法 ,接着分析 getTask方法:

注1 :条件也是成立的,同上;执行decrementWorkerCount 使线程池的线程数减1,此时线程池中的个数等于0,然后 return null,结束 runWorker 中的 while循环。

接着就是执行 runWorker 中finally块中的processWorkerExit方法。

processWorkerExit 方法分析开始

注1:执行tryTerminate,接着分析 tryTerminate中的代码:

tryTerminate分析开始:

tryTerminate 中的 注1 条件不满足,因为线程池的状态是SHUT_DOWN,且workQueue是empty;

注2:条件if (workerCountOf(c) != 0)也不满足,此时线程个数等于0

注3:设置线程池的状态为TIDYING成功,

注4:ctl.set(ctlOf(TERMINATED, 0)) 设置线程池的状态为 TERMINATED

tryTerminate分析结束

执行 processWorkerExit 的注2,由于此时线程池的状态为TERMINATED,所以条件不成立,核心线程对应的 runWorker方法执行完了。

到此,执行 shutDown后的,所以线程(包含核心线程和非核心线程)都回收了。

本篇主要讲了 线程池中任务的执行、线程的复用、线程的回收,以及一些case被调用到的条件。

这篇文章已经太长了,包含shutDown和shutDownNow的区别,下篇再分析啦。

/   总结   /

  1. 如果不执行线程池的shutDown方法且没有调用设置核心线程空闲存活时间,那么只会回收非核心线程,不会回收核心线程,核心线程会一直阻塞

  2. 调用shutDown方法后,所有线程都会回收,这也是为什么大多数我们调用线程池的 execute方法后,会调用shutDown方法

  3. 线程池的执行流程分析

推荐阅读:

WorkManager流程分析和源码解析

我的新书,《第一行代码 第3版》已出版!

排障困难?给你的应用嵌入一个Logcat吧

欢迎关注我的公众号

学习技术或投稿

长按上图,识别图中二维码即可关注


http://www.ppmy.cn/news/877784.html

相关文章

Windows 内核的版本

正如上一节所介绍&#xff0c;Windows 内核经过了20 年的发展&#xff0c;其体系结构并没有大的变化。而Windows 内核中的各个组件在经过了长期发展以后&#xff0c;变得更加优化和成熟。下页表1.1列出了Windows 内核的版本以及相应的操作系统。 表1.1 Windows 内核的版本列表…

解决.Net Framework 在计算机上已安装了更高的 4.x 版本

在发布iis的时候&#xff0c;后端的api需要.Net Framework 4.62的版本支持&#xff0c;可是在使用驱动安装总是提示“这台计算机中已经安装了 .NET Framework 4.6.2 或版本更高的更新。” 网上找了许多方法测试&#xff0c;都无法解决&#xff0c;直到重新安装了vs2019&#…

Windows内核--子系统(3.5)

到底什么是子系统? 子系统是用户层概念。在Windows内核之上&#xff0c;如果想要执行类UNIX应用程序&#xff0c;就是POSIX子系统&#xff0c;如果要类似OS/2环境&#xff0c;就是OS/2子系统。 如何能模拟出不同子系统呢? 一般需要子系统用户态应用程序和相关DLL支援。 对于W…

Linux--内核版本和发行版本

一、区别 1、linux核心只有内核部分&#xff0c;安装完后&#xff0c;用户界面/软件都没有。内核是系统的心脏&#xff0c;是linux中最基层的代码。 2、linux发行版&#xff0c;就是在内核的基础上&#xff0c;加入用户界面&#xff0c;各种软件的支持。比如CenterOS、小红帽…

非核心版本的计算机上_计算机四级网络工程师知识点笔记(备考指南)

计算机四级网络工程师是先要通过计算机三级网络技术. (计算机三级网络技术笔记翻公众号历史文章) 计算机四级是考两个科目 操作系统30个选择题10个多选题 计算机网络30个选择题10个多选题 两科各拿30分以上即可通过,考试时间是90分钟 如果你只想考证不太建议买课程和花太多时间…

Windows内核--WRK和真实的Windows内核源代码差多少?(1.3)

前面有提到WRK是微软官方公布的XP/Server 2003供学习和研究的内核源代码。WRK介绍关于source code如下&#xff1a; WRK源代码已经很完备 WRK v1.2 includes most of the NTOS kernel sources from the latest released version of Windows, which supports the AMD64 architec…

Windows 内部\内核版本与系统版本对照表

个人收集&#xff0c;作备忘录用。会不断完善增加条目 内核版本号系统版本号描述7600Win7 最开始的的Win7版本6.1.7601Win7 pack1win7 sp1版本&#xff0c;进行过一次修订的win718363.778win10 1909Win10 1909 参考&#xff1a;https://blog.csdn.net/caoshangpa/article/detai…

计算机软件的版本分类

目录 简介版本分类α&#xff08;Alpha&#xff09;β&#xff08;beta&#xff09;trialunregistereddemo 正式版releaseregisteredstandarddeluxeEnhancereferenceprofessionalenterpriseUltimate 其他版本updateoem单机版普及版 简介 我们广义上对测试有着三个传统的称呼&a…