并发编程-线程池(二)线程池回收线程

摘要:
然后,我们还需要了解线程池在不同情况下如何回收线程。首先,我们需要理解线程池状态和线程池状态之间的转换:当异步中断空闲线程以调用线程池的shutdown()接口时,当线程池处于shutdown状态时,阻塞队列为空,并且线程池中执行的任务也为空,当线程库处于STOP状态时,当线程池中执行的任务为空时。

【1】https://blog.csdn.net/u013256816/article/details/109213183

  面试 鹅厂 的时候,问到了 线程池如何销毁线程,这题答的不好。

  这个问题考察的是对线程池的理解,在既然了解了线程池在什么时候创建线程。

  那么也要了解线程池在不同情况是如何回收线程,什么时候回收,怎么回收。

一、线程池状态和状态转换

  首先要了解线程池状态和线程池状态之间的转换 https://www.cnblogs.com/Jomini/p/13669993.html

 并发编程-线程池(二)线程池回收线程第1张

Running : 线程池的初始化状态是RUNNING, 线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。

SHUTDOWN线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务,异步中断闲置的的线程

       调用线程池的 shutdown() 接口时,线程池由RUNNING -> SHUTDOWN。  

STOP : 线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。 

    用线程池的 shutdownNow() 接口时,线程池由 (RUNNING or SHUTDOWN ) -> STOP。

    (注: shutdown 和 shutdownNow() 的不同)

TIDYING当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进

行相应的处理;可以通过重载terminated()函数来实现。 

  当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。

  当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

TERMINATED线程池彻底终止,就变成TERMINATED状态。 线程池处在TIDYING状态时,执行完 terminated()之后,就会由 TIDYING -> TERMINATED。

二、线程池回收线程

  首先分为 不调用 shutdown调用 shutdown 的场景,shutdownNow 这里不分析。

   然后分别,分析 Running状态, Shutdown状态, Stop 状态, 下的回收线程。

2.1、不调用 shutdown 的场景下

2.1.1、Running 状态

  [1]、Running状态下,当前工作线程数量多于核心线程,且任务队列为空

  当前线程数量多于核心线程,且任务队列为空,此时满足减少线程数量的判断,并减少一个线程数量,线程池再回收移除一个线程。

  [1.1]、 jdk 1.8源码的具体实现【1】

runWorker(Worker w) 方法

  工作线程启动后,就进入 runWorker(Worker w) 方法,也就是 Running 状态下,在runWorker(Worker w)) 中

  里面是一个while循环,循环判断任务是否为空,若不为空,执行任务;若取不到任务,或发生异常,退出循环,执行processWorkerExit(w, completedAbruptly); 在这个方法里把工作线程移除掉。 

  取任务的来源有两个,一个是firstTask,这个是工作线程第一次跑的时候执行的任务,最多只能执行一次,后面得从 getTask() 方法里取任务。

  getTask() 是关键,在不考虑异常的场景下,返回null,就表示退出循环,结束线程。

   并发编程-线程池(二)线程池回收线程第2张

getTask() 方法

    getTask() 返回null, 有以下两个条件

    第一种情况,线程池的状态已经是STOP,TIDYING, TERMINATED,或者是SHUTDOWN且工作队列为空;

    第二种情况,工作线程数已经大于最大线程数或当前工作线程已超时,且,还有其他工作线程或任务队列为空。这点比较难理解,总之先记住,后面会用。

    在 Running 状态下,是在满足第二种情况,才会返回 null。 

    此时,当前线程数量多于核心线程,则 timed 为 true; 并且,任务队列为空,workQueue.isEmpty() is ture。

    根据下图的源码第二部分,以上条件满足返回 null 的条件,并通过 compareAndDecrementWorkerCount(c) 方法,通过 CAS 机制减少一个线程数量,注意线程还未被回收。

    如果compareAndDecrementWorkerCount(c) 失败,则 continue 进入下一次循环

   并发编程-线程池(二)线程池回收线程第3张

  processWorkerExit(Worker w, boolean completedAbruptly) 方法

    在 runWorker(Worker w) 方法中,getTask 返回 null 后,就可以进入 processWorkerExit 回收线程。

    通过 workers.remove(w) 移除线程,并用了 tryTerminate()

   并发编程-线程池(二)线程池回收线程第4张

tryTerminate()

   第一个判断条件没有一个子条件符合,跳过。第二个条件,工作线程还存在,那么随机中断一条空闲线程。

  并发编程-线程池(二)线程池回收线程第5张

InterruptdleWorkers(boolean onlyOne)

   被移除出工作队列的线程,中断其他线程。

  并发编程-线程池(二)线程池回收线程第6张

2.1.1.2、线程数量大于最大线程数量,多的直接丢弃。

2.2、调用shutdown 的场景下

  这种场景,无论是核心线程还是非核心线程,所有工作线程都会被销毁。

  在调用shutdown()之后,会向所有的空闲工作线程发送中断信号

  并发编程-线程池(二)线程池回收线程第7张

   最终传入false,调用下面这个方法。

  并发编程-线程池(二)线程池回收线程第8张

2.2.1 Running 状态

  2.2.1.1、 Running 状态,任务已全部完成,线程在阻塞等待。在 Running 状态进入 shutdown 状态。

    很简单,中断信号将其唤醒,从而进入下一轮循环。到达条件1处,符合条件,减少工作线程数量,并返回null,由外层结束这条线程。

    这里的decrementWorkerCount()是自旋式的,一定会减1。

    并发编程-线程池(二)线程池回收线程第9张

   2.2.1.2、 Running 状态,任务还没有完全执行完

    调用shutdown()之后,未执行完的任务要执行完毕,池子才能结束。所以此时有可能线程还在工作。

    这里又要分两个阶段讨论

  2.2.1.2-1 、阶段1 任务较多,工作线程都能获得任务

    这里还不涉及到线程退出,可以跳过不看,只是分析一下收到中断信号后线程的表现。

    假设有线程A,正通过getTask()里获取任务。此时A被中断,在获取任务时,无论是poll()还是take(),都会抛出中断异常。异常被捕获,重新进入下一轮循环,只要队列不为空,就可以继续取任务。

    线程A被中断,再次取任务,调用workQueue.poll() or workQueue.take(),不会抛出异常吗?还可以正常取出任务吗?

    这就要看workQueue的实现了。workQueue是BlockingQueue类型,以常见的LinkedBlockingQueue和ArrayBlockingQueue为例,加锁时都是调用lockInterruptibly(),是响应中断的。该方法又调用了AQS的acquireInterruptibly(int arg)。

    acquireInterruptibly(int arg),无论是在入口处判断中断异常,还是在parkAndCheckInterrupt()方法阻塞,被中断唤醒并判断中断异常时,均使用了Thread.interrupted()。这个方法会返回线程的中断状态,并把中断状态重置!也就是说,线程不再是中断状态了,这

样在再次取任务时,

  就不会报错了。

    并发编程-线程池(二)线程池回收线程第10张

   2.2.1.2-2、阶段2 任务刚好要执行完了

   这时任务已经快取完了,比如有4条工作线程,只剩下2个任务,那就可能出现2条线程获得任务,2条线程阻塞。

   假设A,B获得了任务,C,D阻塞。

   A, B接下来的步骤是:

   step1.任务执行完成后,再次getTask(),此时符合条件1,返回null,线程准备被回收。

   step2.processWorkerExit(Worker w, boolean completedAbruptly) 将线程回收。

   阻塞的C,D中的任意一条被中断唤醒后,又会重复step1的动作,周而复始,直到所有阻塞线程都被中断,唤醒。

  

2.2.2、Shutdown 状态

  线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务,且异步中断闲置的的线程

  getTask 返回null, 调用 processWorkerExit(w, completedAbruptly); 移除线程;

  当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。接着会执行terminated()函数。

  执行完 terminated() 之后,就会由 TIDYING -> TERMINATED,线程池被设置为TERMINATED状态。

2.2.3、Stop 状态

  线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。 

2.3、shutdown 和 shutdownNow 区别 : https://blog.csdn.net/horero/article/details/77622951

四、线程池回收线程源码分析

  https://blog.csdn.net/u013256816/article/details/109213183

免责声明:文章转载自《并发编程-线程池(二)线程池回收线程》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇读取并监控文件的变化React 踩坑--input中的value与defaultValue下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

MySQL锁定状态查看命令

1 show processlist; SHOW PROCESSLIST显示哪些线程正在运行。您也可以使用mysqladmin processlist语句得到此信息。如果您有SUPER权限,您可以看到所有线程。否则,您只能看到您自己的线程(也就是,与您正在使用的MySQL账户相关的线程)。如果有线程在update或者insert 某个表,此时进程的stat...

KVM虚拟机快照备份

KVM 快照的定义:快照就是将虚机在某一个时间点上的磁盘、内存和设备状态保存一下,以备将来之用。它包括以下几类: (1)磁盘快照:磁盘的内容(可能是虚机的全部磁盘或者部分磁盘)在某个时间点上被保存,然后可以被恢复。 磁盘数据的保存状态: 在一个运行着的系统上,一个磁盘快照很可能只是崩溃一致的(crash-consistent) 而不是完整一致(clean)...

《深入理解JAVA虚拟机》(一) JVM 结构 + 栈帧 详解

​ 1、程序计数器(Program Counter Register)         线程独有,每个线程都有自己的计数器;由于CPU的任意时刻只能执行所有线程中的一条,所以需要使用程序计数器来支持JVM的并发;另外字节码解释器读取下一行指令、分支、循环、跳转、异常处理等等逻辑都依赖于程序计数器。程序计数器是JVM唯一不存在OutOfMemoryError...

多线程:C#线程同步lock,Monitor,Mutex,同步事件和等待句柄

转自:http://www.cnblogs.com/freshman0216/archive/2008/07/29/1252253.html 本篇从Monitor,Mutex,ManualResetEvent,AutoResetEvent,WaitHandler的类关系图开始, 希望通过本篇的介绍能对常见的线程同步方法有一个整体的认识,而对每种方式的使用细...

JAVA协程 纤程 与Quasar 框架

ava使用的是系统级线程,也就是说,每次调用new Thread(....).run(),都会在系统层面建立一个新的线程,然鹅新建线程的开销是很大的(每个线程默认情况下会占用1MB的内存空间,当然你愿意的话可以用-Xss来调小点),更不要说线程切换带来的开销了 为了节省开销,程序员玩出了很多花样。 最常用的是线程池(线程复用,但是完全无法处理阻塞调用的问题...

window消息机制

剖析Windows消息处理机制 前一段,帮人写了个小控件,又温习了一遍Windows消息处理机制,现在把一些知识点总结出来,供大家参考.1.窗口 Windows程序是由一系列的窗口构成的,每个窗口都有自己的窗口过程,窗口过程就是一个拥有有固定 Signature 的 C函数,具体格式如下: LRESULT CALLBACK WindowProc(HWND...