【深入理解 线程池】
创始人
2024-05-30 16:24:57
0

深入理解 线程池

  • 介绍
  • 源码学习
    • 线程池的类继承体系
    • ThreadPoolExector
      • 核心数据结构
      • 核心配置参数
        • 线程池的执行流程如图:
      • 线程池的优雅关闭
        • 线程池的生命周期
        • 正确关闭线程池的步骤
      • 任务的提交过程分析
      • 任务的执行过程
        • shutdonw() 与任务执行过程综合分析
        • shutdonwNow() 与任务执行过程综合分析
  • 总结

介绍

线程池(Thread Pool) 把一个或多个线程通过统一的方式进行调度和重复使用的技术(采用池化思想)。
优点:
1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
3.提高线程的可管理性

源码学习

线程池的类继承体系

在这里插入图片描述
在类图中,有两个核心的类。ThreadPoolExectorScheduledThreadPoolExecutor。后者不仅可以执行某个任务,还可以周期性地执行任务。向线程池中提交的每个任务,都必须实现Runnable接口。通过最上面的Executor 接口中的execute(Ruunable task)向线程池提交任务。同时,在ExecutorService中,定义了线程池的关闭接口shutdown。还定义了可以有返回值的任务。也就是Callable

ThreadPoolExector

核心数据结构

public class ThreadPoolExecutor extends AbstractExecutorService {//状态变量private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));//存放任务的阻塞队列private final BlockingQueue workQueue;//对线程池内部各种变量进行互斥访问控制private final ReentrantLock mainLock = new ReentrantLock();private final Condition termination = mainLock.newCondition();//线程集合private final HashSet workers = new HashSet();......}

每一个线程是一个Worker对象。Worker继承自AQS。具有锁的一些特性,对于完成线程池的关闭和执行任务起到关键作用。

 private final class Workerextends AbstractQueuedSynchronizerimplements Runnable{/*** This class will never be serialized, but we provide a* serialVersionUID to suppress a javac warning.*/private static final long serialVersionUID = 6138294804551838833L;/** Thread this worker is running in.  Null if factory fails. *///Worker 封装的线程final Thread thread;/** Initial task to run.  Possibly null. */// Worker 接受到的第一个任务Runnable firstTask;/** Per-thread task counter *///Worker 执行完毕的任务个数volatile long completedTasks;.....
}

核心配置参数

 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.acc = System.getSecurityManager() == null ?null :AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}

面试常考:
corePoolSize:在线程池中始终维护的线程个数。
maxPoolSize:在corePoolSize 已满,队列也满的情况下,扩充线程到该值。
keepAliveTime/TimeUnit: maxPoolSize 中的空闲线程,销毁所需要的时间。总线程回收至corePoolSize.
TimeUnit 是时间单位。
blockingQueue:线程池所用的队列类型。一定要设置队列大小。
threadFactory: 线程创建工厂,可以自定义。一般我们采用默认值。
RejectedExecutionHandler:corePoolSize 已满,队列已满,maxPoolSize已满,最后的拒绝策略。

线程池的执行流程如图:

在这里插入图片描述

首先是判断corePoolSize,其次判断blockingQueue是否已满,接着判断maxPoolSize.最后使用拒绝策略。所以 一定要设置blockingQueue的大小。不然会一直往队列中塞任务,撑爆服务器内存然后宕机。

线程池的优雅关闭

线程池的关闭对比于线程的关闭,更加复杂。 比如 当关闭一个线程池的时候,有的线程正在执行某个任务,有的调用者正在向线程池提交任务,并且队列中还有未执行的任务。因此,关闭过程不可能是立刻马上关闭,需要有一个平滑的过渡,这里就涉及线程池的完整生命周期管理。

线程池的生命周期

线程池中,把线程数量(workCount)和线程池状态(runState)这两个变量打包存储在一个字段里,即ctl变量。如下图,最高位的三位存储线程池状态,其余29位存储线程个数。在jdk6中,这两个变量是分开存储。
在这里插入图片描述

    //初始化时,线程池状态为RUNNING,线程数为0private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));//最高的三位表示线程池的状态private static final int COUNT_BITS = Integer.SIZE - 3;private static final int CAPACITY   = (1 << COUNT_BITS) - 1;// runState is stored in the high-order bits//线程池的5钟状态private static final int RUNNING    = -1 << COUNT_BITS;private static final int SHUTDOWN   =  0 << COUNT_BITS;private static final int STOP       =  1 << COUNT_BITS;private static final int TIDYING    =  2 << COUNT_BITS;private static final int TERMINATED =  3 << COUNT_BITS;// Packing and unpacking ctl//从ctl 中分别解包出runState和workerCount private static int runStateOf(int c)     { return c & ~CAPACITY; }private static int workerCountOf(int c)  { return c & CAPACITY; }//rs 即runState,wc即workerCount,两个变量打包成ctl一个变量private static int ctlOf(int rs, int wc) { return rs | wc; }

从上面的代码可以看出,ctl变量被拆成两半,最高的3位用来表示线程池的状态,低的29位表示线程的个数。线程池的状态有5种。分别是RUNNING,SHUTDOWN,STOP和TERMINATED.
5种状态的迁移过程如图:
在这里插入图片描述
线程池有两个关闭函数,shutdown()和shutdownNow(),这两个函数会让线程池切换到不同的状态。在队列为空,线程池也为空之后,进入TIDYING状态。最后执行一个钩子函数terminated(),进入TERMINATED状态。线程池才 “结束生命”。
这里的状态迁移,只能从小到大迁移,不能逆向迁移。

正确关闭线程池的步骤

通过上面的分析,我们了解到 线程池的关闭需要一个过程,在调用shutdown 或者shutdownNow之后,线程池并不会立即关闭,接下来需要调用awaitTermination来等待线程池关闭。关闭线程池的正确步骤如下:
调用完 shutdown或者 shutdownNow后。然后循环调用 awaitTermination()方法来判断线程池的状态。

executor.shutdown();
//调用shutdown()后,调用 awaitTermination
try{boolean loop=true;do{loop=!executor.awaitTermination(2,TimeUnit.SECONDS);//阻塞,直到线程池里所有的任务结束	}while(loop)
}catch(InterruptedException e){}

awaitTermination 函数
不断循环判断线程池是否达到了最终状态TERMINATED,如果达到了,就返回,如果不是,则通过termination条件变量阻塞一段时间。"苏醒"之后继续判断。

public boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException {long nanos = unit.toNanos(timeout);final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {//不断循环for (;;) {//判断线程池的状态是否是TERMINATEDif (runStateAtLeast(ctl.get(), TERMINATED))return true;if (nanos <= 0)return false;//如果不是,进行阻塞nanos = termination.awaitNanos(nanos);}} finally {mainLock.unlock();}}

shutdown 和shutdownNow的区别
1.前者不会清空任务队列,会等待所有任务执行完成,后者会直接清空队列
2.前者只会中断空闲的线程,后者会中断所有的线程。

public void shutdown() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {//判断是否有权限关闭线程池checkShutdownAccess();//设置状态为SHUTDOWNadvanceRunState(SHUTDOWN);//只中断空闲的线程interruptIdleWorkers();//钩子函数,目前是空的,为自定义的线程池个性化扩展onShutdown(); // hook for ScheduledThreadPoolExecutor} finally {mainLock.unlock();}tryTerminate();}
  public List shutdownNow() {List tasks;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {//判断是否有权限关闭线程池checkShutdownAccess();//设置状态为STOPadvanceRunState(STOP);//中断所有线程interruptWorkers();//清空队列tasks = drainQueue();} finally {mainLock.unlock();}tryTerminate();return tasks;}

继续分析下 interruptIdleWorkers()中断空闲线程和 interruptWorkers()中断所有线程

  private void interruptIdleWorkers() {interruptIdleWorkers(false);}private void interruptIdleWorkers(boolean onlyOne) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (Worker w : workers) {Thread t = w.thread;//状态是是非interrutped,并且能拿到锁。work也是继承AQS,说明是空闲的//此时才执行interupt,中断信号if (!t.isInterrupted() && w.tryLock()) {try {t.interrupt();} catch (SecurityException ignore) {} finally {w.unlock();}}if (onlyOne)break;}} finally {mainLock.unlock();}}
private void interruptWorkers() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (Worker w : workers)w.interruptIfStarted();} finally {mainLock.unlock();}}void interruptIfStarted() {Thread t;//只要线程启动,并且状态不是interrupted。就执行中断信号if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {try {t.interrupt();} catch (SecurityException ignore) {}}}

shutdown和shutdownNow 最后都执行了tryTerminate();
tryTerminate不会强行终止线程池,只是做了检测。当 workerCount为0,workerQueue为空时,先把状切换为TIDYING。然后调用钩子函数terminated,随后把状态改成TERMINATED。最后执行 termination.sinaAll().通知前面阻塞在awaitTermination的所有调用线程。

final void tryTerminate() {for (;;) {int c = ctl.get();if (isRunning(c) ||runStateAtLeast(c, TIDYING) ||(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))return;if (workerCountOf(c) != 0) { // Eligible to terminateinterruptIdleWorkers(ONLY_ONE);return;}//当workQueue为空,workCount为0时,才会到这final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {//cas 修改状态为 TIDYINGif (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {try {//调用钩子函数terminated();} finally {//设置状态为 TERMINATEDctl.set(ctlOf(TERMINATED, 0));//唤醒所有线程--在awaitTerminationtermination.signalAll();}return;}} finally {mainLock.unlock();}// else retry on failed CAS}}

任务的提交过程分析

execute

  public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();//当前线程数小于核心线程数,则新增线程处理任务if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;//不管上面的新增线程是否成功,重新更新c的值c = ctl.get();}//到这步,说明上面步骤添加线程失败了,因为多线程的原因,此时线程数量大于核心线程数了。//如果线程池是运行的,此时尝试将任务加入到队列if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}//放入队列失败,尝试创建线程执行else if (!addWorker(command, false))//创建线程失败,说明超过最大线程池数了,采用拒绝策略reject(command);}

addWorker 此函数用于开一个新的线程,如果第二个参数core为true,则用corePoolSize作为上界,如果为false,则用maxPoolSize作为上界。

private boolean addWorker(Runnable firstTask, boolean core) {retry:for (;;) {int c = ctl.get();int rs = runStateOf(c);// Check if queue empty only if necessary.//如果线程池的状态为SHUTDOWN,说明线程池进入了关闭过程// 并且 1. 线程状态已经大于 SHUTDOWN 就直接返回 false,//      2.新加的任务 不是null.也不能加任务了,则直接返回 false//     3. 队列是空的,也不加任务了,直接返回 falseif (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;for (;;) {int wc = workerCountOf(c);if (wc >= CAPACITY ||//线程数超过了上界,则不会创建,直接返回false;wc >= (core ? corePoolSize : maximumPoolSize))return false;//worker加一,则跳出循环if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get();  // Re-read ctl//重新判断c.如果值不相等,说明有其他线程增加了进来。则需要从新循环if (runStateOf(c) != rs)continue retry;// else CAS failed due to workerCount change; retry inner loop}}//到这里说明worker加1了,后面的逻辑就是具体实例化一个worker.boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {//进行初始化w = new Worker(firstTask);final Thread t = w.thread;if (t != null) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// Recheck while holding lock.// Back out on ThreadFactory failure or if// shut down before lock acquired.int rs = runStateOf(ctl.get());//再次校验rs 的值。即线程池的状态//线程池的状态还是 RUNNING,//如果是 SHUTDOWN.可以加null任务if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {if (t.isAlive()) // precheck that t is startable//线程还没启动,此时是alive().则抛出异常throw new IllegalThreadStateException();//把线程加入到线程集合workers.add(w);int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}if (workerAdded) {//此时才启动该线程t.start();workerStarted = true;}}} finally {if (! workerStarted)//如果加入线程失败。则把workerCount减一addWorkerFailed(w);}return workerStarted;}

任务的执行过程

在上面的任务提交过程中,可能会开启一个新的Worker,并把任务本身作为firstTask赋给该Worker。但对于一个Worker来说,不是只执行一个任务,而是源源不断地从队列中取任务执行,这是一个不断循环的过程。

    private final class Workerextends AbstractQueuedSynchronizerimplements Runnable{/*** This class will never be serialized, but we provide a* serialVersionUID to suppress a javac warning.*/private static final long serialVersionUID = 6138294804551838833L;/** Thread this worker is running in.  Null if factory fails. *///对于的线程final Thread thread;/** Initial task to run.  Possibly null. *///对于的任务Runnable firstTask;/** Per-thread task counter *///统计线程完成的任务数量volatile long completedTasks;/*** Creates with given first task and thread from ThreadFactory.* @param firstTask the first task (null if none)*/Worker(Runnable firstTask) {//初始值设置的是-1setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;this.thread = getThreadFactory().newThread(this);}/** Delegates main run loop to outer runWorker  */public void run() {//调用的是ThreadPoolExecutor 的runWorker方法runWorker(this);}}

runWorker

 //运行的任务有两种类型 一种是线程数不到核心线程数或者队列满了线程数不到最大线程数,直接new Work执行
// 从队列中取final void runWorker(Worker w) {Thread wt = Thread.currentThread();// 从worker中取的任务Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // allow interruptsboolean completedAbruptly = true;try {//不断循环从队列中获取任务执行//task 首先从work中取,取不到然后再从队列中取(getTask())while (task != null || (task = getTask()) != null) {//执行任务先加锁。此处对应了shutdown 来判断线程是否是空闲时的操作tryLock.//如果能获取到锁,说明没有任务执行,线程是空闲的。w.lock();// If pool is stopping, ensure thread is interrupted;// if not, ensure thread is not interrupted.  This// requires a recheck in second case to deal with// shutdownNow race while clearing interrupt//对线程池和线程状态的校验判断,if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())//不符合就interupt,给自己发送中断信号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;//完成任务数加1w.completedTasks++;//释放锁w.unlock();}}//判断是正常退出还是异常退出,用于finally里面里面进行worker退出的逻辑处理completedAbruptly = false;} finally {//worker退出processWorkerExit(w, completedAbruptly);}}

shutdonw() 与任务执行过程综合分析

把任务的执行过程和上面的线程池的关闭过程结合起来进行分析。当调用shutdown()的时候,可能会出现几种情况
场景一:
当调用shutdown时候,所有线程都处于空闲状态。
这意味着任务队列一定是空的。此时,所有线程都会阻塞在getTask()的地方,然后,所有线程都会收到interruptIdleWorkers()发过来的中断信号。getTask()会响应中断,然后返回null.所有Worker都会退出while循环,然后执行processWorkerExit;

场景二:
当调用shutdown时,所有线程都处于忙碌状态
此时队列可能为空,也可能是非空的,interruptIdleWorkers()内部的tryLock调用失败。什么都不会做,直至队列任务为空。interruptIdleWorkers()内部的tryLock调用成功。此时和场景一一样。

场景三:
当调用shutdown时,部分线程忙碌,部分线程空闲。
有部分线程空闲,说明队列是空的。忙碌的线程按照场景一处理,不忙碌的线程按照场景一处理。

getTask()函数

private Runnable getTask() {boolean timedOut = false; // Did the last poll() time out?for (;;) {int c = ctl.get();int rs = runStateOf(c);// Check if queue empty only if necessary.//rs >= SHUTDOWN 说明调用了shutdown。// 并且队列为空 或者 rs >= STOP 调用了shutdownNow ,则返回null if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount();return null;}int wc = workerCountOf(c);// Are workers subject to culling?boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;if ((wc > maximumPoolSize || (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) {if (compareAndDecrementWorkerCount(c))return null;continue;}try {//1.队列为空,就会阻塞在此处的poll或者take。poll 带超时时间。take不带超时时间。//2.一旦收到中断信号,就会进入catch代码,设置 timedOut=false;Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();if (r != null)return r;timedOut = true;} catch (InterruptedException retry) {//可以响应 interruptIdleWorkers 发出的中断信号 timedOut = false;}}}

shutdonwNow() 与任务执行过程综合分析

shutdownNow()比较粗暴。和shutdown()相比,多了一个清除队列的操作。少了一些判断而已。

总结

整体来讲,线程池的设计还是比较复杂的。本文首先从整体架构来介绍了线程池,然后逐步从线程池的提交执行和关闭等操作进行了源码分析。希望能对大家理解线程池有所帮助。

相关内容

热门资讯

提振消费需多角度化解关键问题 转自:中华工商时报    近日,国务院新闻办公室举行新闻发布会,介绍“一揽子金融政策支持稳市场稳预期...
以法治为引领,推进文化产业提质... 转自:中华工商时报    民营经济促进法作为我国首部专门关于民营经济发展的基础性法律,让广大民营企业...
原来是星链卫星过境 成都上空移动的“珍珠串”是啥?星链卫星过境成都。“Cat猫山王”摄制图罗乐4月30日晚上星链卫星过境...
迪士尼、阿布扎比:处于世界“十... 2023年12月2日,阿拉伯联合酋长国阿布扎比的法拉利世界建筑。  迪士尼上周宣布其最新的主题公园将...
史彭元:年少成名擅长“小苦瓜”... 《隐秘的角落》后再演悬疑剧演员史彭元图据史彭元微博《借命而生》海报。姚斌彬与看守所管教杜湘东(秦昊饰...
西宁房博会:激活省城楼市新生态 西海新闻记者 周建萍 从2024年深秋到2025年初夏,西宁市以3次大型房博会为支点,撬动了房地产市...
“生态研学专航” 开辟青藏高原... 本报海北讯 (记者 王晶 通讯员 铁晓林 贺玉才) “祁连县拥有清晰的地质演化特征和壮丽的地貌景观,...
月白与赪霞 ◎成都七中育才学校2024级13班曾梓欣  我所能找到的可以形容阿坝清晨与黄昏的词,恐怕只有月白与赪...
仁心织锦护苍生 薪火相传谱华章 2025年护士节表彰大会隆重举行。西海新闻记者 徐变银 文/图五月的鲜花为天使绽放,五月的赞歌为生命...
中美经贸高层会谈取得了实质性进... 中方发布会:  新华社日内瓦5月11日电5月10日至11日,中美双方在瑞士日内瓦举行经贸高层会谈。中...
31个产品获“天府名品”品牌授... “魅力天府品牌之夜”活动举行,拉开中国品牌日四川活动序幕  发布《四川品牌发展报告2025》(以下简...
新一波“台球热”催生千亿赛道 “好一点的台位要提前三天预订”台球馆内人气火爆。受访者供图  斯诺克世锦赛冠军的奖杯,终于被中国选手...
署名文章:从宏观调控看中国经济... 转自:新华社客户端新华社北京5月11日电 题:从宏观调控看中国经济基本面清华大学中国发展规划研究院常...
成都这家企业用“氢”与“翼”开... 产品远销35国 今年销售额有望破亿元大关市民骑氢动力两轮车出行。杨浩  一架满载货物的物流无人机从位...
罗东川会见中国中车集团董事长孙... 本报讯(记者 田得乾)5月11日,省委副书记、省长罗东川在西宁会见中国中车集团有限公司董事长孙永才一...
大庆消防员演示班组灭火作业,全... 5月9日,在黑龙江省大庆市,国家消防救援局大庆航空救援支队的消防员向人们演示班组灭火作业。5月12日...
印巴同意停火 联合国表示欢迎 5月10日,在巴基斯坦东部旁遮普省木尔坦市,人们在巴基斯坦和印度宣布停火后庆祝。新华社发  印度和巴...
四川在建规模最大单体光伏项目开...   5月9日,四川省在建规模最大的单体光伏项目——华电新能中咱120万千瓦光伏发电项目正式开工建设,...
来成都工作 有哪些重点人才政策... 转自:成都日报锦观来成都工作 有哪些重点人才政策福利? 日前,一年一度的“蓉漂人才日”到来。活...
皮洛遗址保护利用项目正式开工 总投资约2.4亿元 四川稻城打造史前文明新地标皮洛遗址保护利用项目效果图。图据稻城融媒  华西都市报...