7000字+24张图带你彻底弄懂线程池(1)

简介: 7000字+24张图带你彻底弄懂线程池(1)



今天跟大家聊一聊无论是在工作中常用还是在面试中常问的线程池,通过画图的方式来彻底弄懂线程池的工作原理,以及在实际项目中该如何自定义适合业务的线程池。

一、什么是线程池

线程池其实是一种池化的技术的实现,池化技术的核心思想其实就是实现资源的一个复用,避免资源的重复创建和销毁带来的性能开销。在线程池中,线程池可以管理一堆线程,让线程执行完任务之后不会进行销毁,而是继续去处理其它线程已经提交的任务。

线程池的好处:

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

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

二、线程池的构造

Java中主要是通过构建ThreadPoolExecutor来创建线程池的,接下来我们看一下线程池是如何构造出来的。

线程池构造参数

  • corePoolSize:线程池中用来工作的核心的线程数量。
  • maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
  • keepAliveTime:超出 corePoolSize 后创建的线程存活时间或者是所有线程最大存活时间,取决于配置。
  • unit:keepAliveTime 的时间单位。
  • workQueue:任务队列,是一个阻塞队列,当线程数已达到核心线程数,会将任务存储在阻塞队列中。
  • threadFactory :线程池内部创建线程所用的工厂。
  • handler:拒绝策略;当队列已满并且线程数量达到最大线程数量时,会调用该方法处理该任务。

线程池的构造其实很简单,就是传入一堆参数,然后进行简单的赋值操作。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

三、线程池的运行原理

说完线程池的核心构造参数的意思,接下来就来画图讲解这些参数在线程池中是如何工作的。

线程池刚创建出来是什么样子呢,如下图

不错,刚创建出来的线程池中只有一个构造时传入的阻塞队列而已,此时里面并没有的任何线程,但是如果你想要在执行之前已经创建好核心线程数,可以调用prestartAllCoreThreads方法来实现,默认是没有线程的。

当有线程通过execute方法提交了一个任务,会发生什么呢?

提交任务的时候,其实会去进行任务的处理

首先会去判断当前线程池的线程数是否小于核心线程数,也就是线程池构造时传入的参数corePoolSize。

如果小于,那么就直接通过ThreadFactory创建一个线程来执行这个任务,如图

当任务执行完之后,线程不会退出,而是会去从阻塞队列中获取任务,如下图

接下来如果又提交了一个任务,也会按照上述的步骤,去判断是否小于核心线程数,如果小于,还是会创建线程来执行任务,执行完之后也会从阻塞队列中获取任务。这里有个细节,就是提交任务的时候,就算有线程池里的线程从阻塞队列中获取不到任务,如果线程池里的线程数还是小于核心线程数,那么依然会继续创建线程,而不是复用已有的线程。

如果线程池里的线程数不再小于核心线程数呢?那么此时就会尝试将任务放入阻塞队列中,入队成功之后,如图

这样在阻塞的线程就可以获取到任务了。

但是,随着任务越来越多,队列已经满了,任务放入失败了,那怎么办呢?

此时就会判断当前线程池里的线程数是否小于最大线程数,也就是入参时的maximumPoolSize参数

如果小于最大线程数,那么也会创建非核心线程来执行提交的任务,如图

所以,从这里可以发现,就算队列中有任务,新创建的线程还是优先处理这个提交的任务,而不是从队列中获取已有的任务执行,从这可以看出,先提交的任务不一定先执行。

但是不幸的事发生了,线程数已经达到了最大线程数量,那么此时会怎么办呢?

此时就会执行拒绝策略,也就是构造线程池的时候,传入的RejectedExecutionHandler对象,来处理这个任务。

RejectedExecutionHandler的实现JDK自带的默认有4种

  • AbortPolicy:丢弃任务,抛出运行时异常
  • CallerRunsPolicy:由提交任务的线程来执行任务
  • DiscardPolicy:丢弃这个任务,但是不抛异常
  • DiscardOldestPolicy:从队列中剔除最先进入队列的任务,然后再次提交任务

线程池创建的时候,如果不指定拒绝策略就默认是AbortPolicy策略。当然,你也可以自己实现RejectedExecutionHandler接口,比如将任务存在数据库或者缓存中,这样就数据库或者缓存中获取到被拒绝掉的任务了。

到这里,我们发现,线程池构造的几个参数corePoolSize、maximumPoolSize、workQueue、threadFactory、handler我们都在上述的执行过程中讲到了,那么还差两个参数keepAliveTime和unit(unit是keepAliveTime的时间单位)没讲到,所以keepAliveTime是如何起到作用的呢,这个问题留到后面分析。

说完整个执行的流程,接下来看看execute方法代码是如何实现的。

execute方法

  • workerCountOf(c)<corePoolSize:这行代码就是判断是否小于核心线程数,是的话就通过addWorker方法,addWorker就是添加线程来执行任务。
  • workQueue.offer(command):这行代码就表示尝试往阻塞队列中添加任务
  • 添加失败之后就会再次调用addWorker方法尝试添加非核心线程来执行任务
  • 如果还是添加非核心线程失败了,那么就会调用reject(command)来拒绝这个任务。

最后再来另画一张图总结execute执行流程

四、线程池中线程实现复用的原理

线程池的核心功能就是实现了线程的重复利用,那么线程池是如何实现线程的复用呢?

线程在线程池内部其实是被封装成一个Worker对象

Worker继承了AQS,也就是有一定锁的特性。

创建线程来执行任务的方法上面提到是通过addWorker方法创建的。在创建Worker对象的时候,会把线程和任务一起封装到Worker内部,然后调用runWorker方法来让线程执行任务,接下来我们就来看一下runWorker方法。

启动线程处理任务

从这张图可以看出线程执行完任务不会退出的原因,runWorker内部使用了while死循环,当第一个任务执行完之后,会不断地通过getTask方法获取任务,只要能获取到任务,就会调用run方法,继续执行任务,这就是线程能够复用的主要原因。

但是如果从getTask获取不到方法的时候,最后就会调用finally中的processWorkerExit方法,来将线程退出。

这里有个一个细节就是,因为Worker继承了AQS,每次在执行任务之前都会调用Worker的lock方法,执行完任务之后,会调用unlock方法,这样做的目的就可以通过Woker的加锁状态就能判断出当前线程是否正在运行任务。如果想知道线程是否正在运行任务,只需要调用Woker的tryLock方法,根据是否加锁成功就能判断,加锁成功说明当前线程没有加锁,也就没有执行任务了,在调用shutdown方法关闭线程池的时候,就用这种方式来判断线程有没有在执行任务,如果没有的话,来尝试打断没有执行任务的线程。

相关文章
|
数据安全/隐私保护
|
3月前
|
人工智能 安全 API
Cline + Cursor 组合拳:从代码清理到 Git 提交,我的标准化发布流程
本文介绍如何用 Cline(VS Code 插件)与 Cursor(AI 编程编辑器)组合,构建标准化发布流程:自动执行代码规范检查(ESLint/TSC)、清理 console.log、修复问题、运行 Vitest 测试、生成 Conventional Commits 提交信息。全程可控、可审计,单次耗时仅 5–8 分钟,大幅提升代码质量与团队协作效率。(239 字)
280 1
|
9月前
|
算法 编译器 C语言
【2026最新】MinGW64官网下载安装保姆级教程(附安装包+图文并茂)
MinGW-w64 是 Windows 下的轻量级 GNU 编译器集合,支持 C/C++ 编译,无需虚拟机即可生成独立 exe 文件。它体积小、免安装、静态链接运行库,兼容 Win10/11 与旧系统,配置简单,广泛用于算法开发、刷题及教学场景。
15763 5
|
机器学习/深度学习 安全 数据可视化
基于YOLOv8的反光衣服检测识别项目|完整源码数据集+PyQt5界面+完整训练流程+开箱即用!
本项目利用YOLOv8实现了反光衣服检测与识别,通过摄像头或视频输入,可以实时识别人员所穿衣物是否为反光衣服,适用于工地安全监管、交通巡逻、夜间施工安全防护等场景。
基于YOLOv8的反光衣服检测识别项目|完整源码数据集+PyQt5界面+完整训练流程+开箱即用!
|
机器学习/深度学习 人工智能 算法
MIDI-3D:单图秒变3D场景!40秒生成360度空间,多实例扩散黑科技
MIDI-3D 是一种先进的 AI 3D 场景生成技术,能够将单张图像快速转化为高保真度的 360 度 3D 场景,具有强大的全局感知能力和细节表现力,适用于游戏开发、虚拟现实、室内设计等多个领域。
826 18
MIDI-3D:单图秒变3D场景!40秒生成360度空间,多实例扩散黑科技
|
传感器 机器学习/深度学习 人工智能
《软体机器人与仿生机器人的人工智能控制技术:挑战与突破》
在科技飞速发展的今天,软体机器人和仿生机器人成为研究焦点。软体机器人面临复杂环境适应性、力学性能控制及能源供给等挑战;仿生机器人则需解决生物灵感转化、系统集成及伦理问题。人工智能控制技术如智能算法、传感器创新和人机交互的发展为这些机器人带来新机遇,但也需关注伦理和社会影响。未来需跨学科合作,推动机器人技术进步并确保其符合人类利益。
762 8
《软体机器人与仿生机器人的人工智能控制技术:挑战与突破》
|
网络协议 CDN 网络安全
真正的计算机网络
本文深入浅出地介绍了计算机网络的基本概念、服务提供商(如IDC、ISP)、公共与专用网络电路、IP地址及其归属、DNS解析机制、CDN内容分发网络以及代理等相关知识。文章不仅涵盖了基础概念,还探讨了网络优化和实际应用中的常见问题,帮助读者从不同角度理解计算机网络的运作原理。更多详细内容请参阅我的博客:[Real Computer Network](https://blog.timerring.com/posts/real-computer-network/)。
931 18
|
开发框架 Java .NET
Python中main函数:代码结构的基石
在Python中,`main`函数是程序结构化和模块化的重要组成部分。它实现了脚本执行与模块导入的分离,避免全局作用域污染并提升代码复用性。其核心作用包括:标准化程序入口、保障模块复用及支持测试驱动开发(TDD)。根据项目复杂度,`main`函数有基础版、函数封装版、参数解析版和类封装版四种典型写法。 与其他语言相比,Python的`main`机制更灵活,支持同一文件作为脚本运行或模块导入。进阶技巧涵盖多文件项目管理、命令行参数处理、环境变量配置及日志集成等。此外,还需注意常见错误如全局变量污染和循环导入,并通过延迟加载、多进程支持和类型提示优化性能。
1307 0
|
SQL 存储 Oracle
oracle发展史
oracle发展史
846 0