首页 > 编程语言 >线程池源码学习

线程池源码学习

时间:2024-01-16 22:12:42浏览次数:25  
标签:状态 队列 学习 任务 源码 线程 执行 方法

使用线程池的好处

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

ThreadPoolExecutor 分析

ThreadPoolExecutor 继承 AbstractExecutorService,也就是实现了 ExecutorService 接口

几个重要的字段

image-20231225233744151

其中 ctl 字段包含两部分信息:

  • 线程池的运行状态(runState)
  • 线程池内有效线程的数量(workCount)

高三位保存 runState,低 29 位保存 workCount

线程池共有五种状态:

  1. RUNNING
  2. SHUTDOWN

不接受新任务,可以继续处理阻塞队列里面的任务,当阻塞队列为空时,会减少工作线程。

  1. STOP

不接受新任务,也不会处理队列中的任务,会中断正在处理任务的线程。

  1. TIDYING

所有任务都终止,workCount = 0,进入该状态后会调用 terminate() 方法进入 TERMINATED 状态。

  1. TERMINATED

线程池状态的转换过程:

threadpool-status.png

ThreadPoolExecutor 构造方法

image-20231225233655469

  • corePoolSize

​ 核心线程数,当提交任务时,如果 workCount 小于核心线程数则创建线程来处理任务;当 workCount 大于核心线程数,小于最大线程数时,将任务放到 workQueue 队列中等待执行;当队列满了,workCount 大于核心线程数并且小于最大线程数,此时创建线程来处理任务,大于核心线程数以外的线程超过 keepAliveTime 后还没有任务处理则回收该线程;如果 队列已满且workCount 大于最大线程数,此时的用 handle 拒绝策略拒绝任务。

  • maximumPoolSize
  • keepAliveTime
  • unit
  • workQueue
  • threadFactory
  • handler

Execute 方法

image-20231225233620371

主要的代码逻辑的流程为:

image-20231225234401364

addWorker 方法

  1. 核心线程数判断场景:判断当前工作线程数是否小于核心线程数,如果是则添加 woker 对象,并执行任务
  2. 最大线程数判断场景:判断当前工作线程是否小于最大线程数,如果是则添加 worker 对象,并执行任务

Worker 类

Worker 类继承了 AQS,并且实现了 Runnable 接口。其中 firstTask 表示初始化传入的任务,thread 为初始化通过 threadFactory 创建出来的线程。因为创建线程的时候 将当前实现了 Runnable 接口的类Worker 传入,因此线程启动后,将执行 Worker 对象的 run 方法。

问题:为什么 Woker 需要继承 AQS,直接使用 ReentrantLock 可以吗?

  1. lock 方法一旦获取了独占锁,表示当前线程正在执行任务中;

  2. 如果正在执行任务,则不应该中断线程;

  3. 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断;

  4. 线程池在执行shutdown方法或t ryTerminate 方法时会调用 interruptIdleWorkers 方法来中断空闲的线程,interruptIdleWorkers 方法会使用 tryLock 方法来判断线程池中的线程是否是空闲状态;

  5. 之所以设置为不可重入,是因为我们不希望任务在调用像 setCorePoolSize 这样的线程池控制方法时重新获取锁。如果使用ReentrantLock,它是可重入的,这样如果在任务中调用了如 setCorePoolSize 这类线程池控制的方法,会中断正在运行的线程。

    以下是调用 interruptIdleWorkers 的方法

    image-20231226233446127

所以,Worker继承自AQS,用于判断线程是否空闲以及是否可以被中断。主要是看 tryAcquire 方法的实现。

image-20231226225856833

runWorker 方法

总结一下runWorker方法的执行过程:

  1. while循环不断地通过getTask()方法获取任务;
  2. getTask()方法从阻塞队列中取任务;
  3. 如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态;
  4. 调用task.run()执行任务;
  5. 如果task为null则跳出循环,执行processWorkerExit()方法;
  6. runWorker方法执行完毕,也代表着Worker中的run方法执行完毕,销毁线程。

这里的beforeExecute方法和afterExecute方法在ThreadPoolExecutor类中是空的,留给子类来实现。

completedAbruptly变量来表示在执行任务过程中是否出现了异常,在processWorkerExit方法中会对该变量的值进行判断。

image-20231227090002141

getTask 方法

getTask 方法是在线程启动执行 run 方法,run 方法里面调用 Worker 类的 runWorker 方法,runWorker 方法里面线程不断的执行任务,任务的来源就是从 getTask 方法中获取。

首先会进行线程池状态判断,如果状态 >= SHUTDOWN,也就是非 Running 状态,需要进行一下判断:

  1. rs >= STOP,表示线程池正在 STOP(STOP 状态的线程池会中断正在处理任务的线程,不管队列里是否还有任务)
  2. 阻塞队列为空,表示线程池执行了 SHUTDOWN (这个状态的线程池会继续处理阻塞队列的任务)

需要注意的是 keepAliveTime 在这个方法里面起作用,当工作线程数大于核心线程数以后就需要进行超时控制。超时控制是通过阻塞队列 workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)方法控制,核心线程数是通过 workQueue.take 一直阻塞获取任务。

image-20231227082912792

processWorkerExit方法

如果上述 getTask() 方法返回为 null,则会进入该方法。该方法主要做了这么几件事:

  1. 将当前工作线程完成的任务数添加至线程池总得完成任务数里面
  2. 从 workers 列表里面移除当前工作线程
  3. 调用 tryTerminate() 方法
  4. 判断工作线程是否抛出异常,如果是直接调用 addWorker() 方法,如果不是则判断当前工作线程数是否小于核心线程数,如果是则调用 addWorker() 方法

tryTerminate 方法

主要作用就是根据线程池的状态判断是否结束线程池

当前线程池的状态为以下几种情况时,直接返回:

  1. RUNNING,因为还在运行中,不能停止;
  2. TIDYING或TERMINATED,因为线程池中已经没有正在运行的线程了;
  3. SHUTDOWN并且等待队列非空,这时要执行完workQueue中的task;

当线程池的状态为 SHUTDOWN 并且工作线程数不为 0,且阻塞队列里面为空的时候就会调用interruptIdleWorkers(ONLY_ONE)

image-20231229003345104

shutdown 方法

shutdown 方法要将线程池切换到 SHUTDOWN 状态,并调用 interruptIdleWorkers 方法请求中断所有空闲的 worker ,最后调用 tryTerminate 尝试结束线程池。

注意:

  1. getTask()方法没有加锁,run 方法里面加锁了,是为了线程在 getTask()方法获取 take 任务阻塞的时候,可以通过线程中断机制进行中断。
  2. 在 shutdown 方法中,中断机制又是通过 interruptIdleWorkers 方法进行的,遍历所有工作线程,找到空闲线程,执行中断 interrupt()方法。

image-20240102232839538

interruptIdleWorkers 方法

遍历所有工作线程,找到第一个空闲线程,也就是 w.tryLock()方法获取到锁的线程就代表该线程没有在执行任务

image-20240102233951680

shutdownNow 方法

和 showdown 方法类似,差异点在于:

  1. 设置线程池状态为 stop;
  2. 中断所有工作线程,无论是否在执行任务;
  3. 取出阻塞队列里面未执行的任务并返回。

image-20240102234519869

线程池的监控

动态线程池,以及线程池监控的实现,就是通过线程池提供的内置方法来实现的:

  • getTaskCount:线程池已经执行和未执行的任务总数;
  • getCompletedTaskCount:线程池已经执行完了的任务数;
  • getLargestPoolSize:线程池曾经创建过得最大线程数;
  • getPoolSize:线程池当前的线程数量;
  • getActiveCount:当前线程池正在执行任务的线程数量。

同时,ThreadPoolExecutor 提供了多个子类实现的方法,比如 beforeExecute 方法, afterExecute 方法以及 terminated 方法,可以用于任务执行耗时统计。

补充内容-线程状态

image-20231225234118439

标签:状态,队列,学习,任务,源码,线程,执行,方法
From: https://www.cnblogs.com/fisher1994/p/17968655

相关文章

  • 机器学习-概率图模型系列-最大熵模型-37
    目录1.熵的定义2.最大熵模型算法3.逻辑回归与最大熵之间的关系参考:https://www.cnblogs.com/pinard/p/6972299.html最大熵模型(maximumentropymodel,MaxEnt)也是很典型的分类算法了,它和逻辑回归类似,都是属于对数线性分类模型。在损失函数优化的过程中,使用了和支持向量机......
  • 搜索学习笔记+杂题 (进阶一 dfs/bfs的进阶)
    前言:没啥好说的了。所以只能来写博客了。搜索杂题:相关题单:戳我二、进阶dfs/bfs1、dfs进阶——折半搜索(meetinthemiddle)由于深搜的时间复杂度在每种状态有两个分支的情况下是\(O(2^n)\)。所以一般暴力深搜的数据范围就在\(20-25\)之间。而对于有一大类的题,它的搜索思......
  • 机器学习周刊第六期:哈佛大学机器学习课、Chatbot Ul 2.0 、LangChain v0.1.0、Mixtral
    ---date:2024/01/08---吴恩达和Langchain合作开发了JavaScript生成式AI短期课程:《使用LangChain.js构建LLM应用程序》大家好,欢迎收看第六期机器学习周刊本期介绍10个内容,涉及Python、机器学习、大模型等,目录如下:1、哈佛大学机器学习课2、第一个JavaScript生成......
  • C++学习日记 2024-1-16
    开始学习C++几天了,之前没有记录,从现在开始,记录一下学习过程复习与回忆:1.引用与指针共同优点:只用引用与指针,在传递参数时,可以减少拷贝,减少内存消耗,提高效率指针优点:指针比引用更强大,所有引用能做的事,指针都能做,指针缺点:危险,指针可以为空,指针指向地址,同一地址可以......
  • 我的学习日
    markdown学习二级标题三级标题四级标题字体加粗**Hello,World!**字体斜体Hello,World!字体加粗+斜体Hello,World!删除线哈哈哈哈哈哈引用阿巴分割线图片![醒狮](C:\Users\35839\Pictures\Screenshots\屏幕截图2023-12-22220732.png)超链接点击打开百度......
  • AC 自动机学习笔记
    AC自动机学习笔记AC自动机可以用于解决字符串上的出现次数,出现位置问题。结合了Trie树和KMP的思想,在\(O(n)\)的时间内完成查询。相较于KMP的好处在于,AC自动机不仅速度快,而且支持多个模式串同时在一个文本串内查询。算法前置知识:Trie树,KMP,自动机基本概念。构建AC......
  • 爬虫-多线程抓取图片
    一、目的利用多线程的方式爬取图片,地址:其他电脑动态壁纸-其他桌面动态壁纸-元气壁纸(cheetahfun.com)二、分析F12分析网页结构,图片的地址都在class="flexflex-wrapjustify-betweenfont-normal"标签中的li里面,只需要在a标签中img中  根据前面学过的内容,......
  • 寒假学习day1
    1.1Spark为何物Spark是当今大数据领域最活跃、最热门、最高效的大数据通用计算平台之一。Hadoop之父DougCutting指出:UseofMapReduceengineforBigDataprojectswilldecline,replacedbyApacheSpark(大数据项目的MapReduce引擎的使用将下降,由ApacheSpark......
  • 1.16学习进度
    sparkde四大特点   速度快:比hadoop的mapreduce快100倍;spark处理数据时,可以将中间处理结果存储到内存中;spark提供了非常丰富分算子,可以做到复杂任务在一个spark程序中完成   易于使用   通用性强:spark提供了sparksql、sparkstreaming、mlib及graphx在内的多个工具库......
  • openGauss学习笔记-199 openGauss 数据库运维-常见故障定位案例-Lock wait timeout
    openGauss学习笔记-199openGauss数据库运维-常见故障定位案例-Lockwaittimeout199.1执行SQL语句时,提示Lockwaittimeout199.1.1问题现象执行SQL语句时,提示“Lockwaittimeout”。ERROR:Lockwaittimeout:thread140533638080272waitingforShareLockonrelat......