深入理解高并发编程 - 深度解析 ThreadPoolExecutor 类

ThreadPoolExecutor 是 Java 标准库中用于创建和管理线程池的核心类之一。它实现了 ExecutorService 接口,提供了丰富的线程池管理功能。下面将通过源码解析来深入了解 ThreadPoolExecutor 类的工作原理和各个重要部分。

可以在 Java 源代码中找到 ThreadPoolExecutor 类的实现,位于 java.util.concurrent 包中。

以下是 ThreadPoolExecutor 类的一些关键概念和部分:

1、构造函数:

ThreadPoolExecutor 类提供了几个不同的构造函数,允许传递核心线程数、最大线程数、空闲线程存活时间、任务队列等参数。这些参数决定了线程池的基本行为。
下面是几个常用的构造函数及其参数的解释:

核心线程数 (corePoolSize):
核心线程数表示线程池中始终保持存活的线程数量。这些线程会一直存在,即使它们处于空闲状态。线程池会根据任务数量自动创建新线程,直到核心线程数达到上限。最大线程数 (maximumPoolSize):
最大线程数表示线程池中最多可以同时存在的线程数量。当核心线程数已满,且任务队列也已满时,线程池会创建新线程,直到最大线程数达到上限。如果达到最大线程数后仍有更多任务到达,根据饱和策略进行处理。空闲线程存活时间 (keepAliveTime):
空闲线程存活时间表示当线程池中的线程数超过核心线程数,并且这些线程处于空闲状态时,它们会被保留的时间。超过此时间后,多余的空闲线程将被终止,从而节省系统资源。时间单位 (unit):
时间单位用于指定空闲线程存活时间的单位,可以是秒、毫秒等。任务队列 (workQueue):
任务队列用于存储等待执行的任务。线程池可以使用不同类型的队列,如 BlockingQueue 的各种实现。任务队列决定了等待执行的任务数量。线程工厂 (threadFactory):
线程工厂用于创建新线程,默认使用 Executors.defaultThreadFactory()。可以自定义线程工厂来创建线程,从而指定线程的名称、优先级等属性。饱和策略 (handler):
当线程池和任务队列都已满,新的任务到达时,饱和策略定义了如何处理这些任务。线程池提供了几种内置的饱和策略,如 AbortPolicy、CallerRunsPolicy、DiscardPolicy 以及 DiscardOldestPolicy。

2、线程池状态:

ThreadPoolExecutor 的内部维护了几种状态,包括 RUNNING、SHUTDOWN、STOP、TIDYING 和 TERMINATED。线程池在不同的状态下会有不同的行为,例如当调用 shutdown 方法时,线程池会从 RUNNING 状态转变为 SHUTDOWN 状态,不再接受新的任务。

RUNNING(运行中):
在 RUNNING 状态下,线程池处于正常运行状态,可以接受新任务并执行已提交的任务。在这个状态下,核心线程数和非核心线程数都可以创建和执行任务。SHUTDOWN(关闭中):
当调用线程池的 shutdown 方法时,线程池会进入 SHUTDOWN 状态。在这个状态下,线程池不再接受新任务,但会继续执行已提交的任务,包括等待队列中的任务。STOP(立即停止):
当调用线程池的 shutdownNow 方法时,线程池会进入 STOP 状态。在这个状态下,线程池会尝试中断所有正在执行的线程,并清空任务队列。TIDYING(整理中):
当线程池状态从 SHUTDOWN 转变为 TIDYING,表示线程池已经停止接受新任务,正在执行中的任务也已经完成,处于整理和清理状态。在这个状态下,线程池会执行一些清理操作,例如中断空闲线程。TERMINATED(已终止):
当线程池状态从 TIDYING 转变为 TERMINATED,表示线程池已经彻底终止,所有任务都已执行完毕,并且线程池中的所有线程都已销毁。在这个状态下,线程池不再执行任何操作。

3、任务队列:

任务队列用于存储等待执行的任务。ThreadPoolExecutor 允许使用不同类型的队列,如 BlockingQueue 的各种实现,包括 LinkedBlockingQueue、ArrayBlockingQueue 等。这些队列控制了等待执行的任务数量。
以下是几种常见的任务队列类型及其特点:

LinkedBlockingQueue(链式阻塞队列):
LinkedBlockingQueue 是一个基于链表的无界阻塞队列,它可以存储无限数量的任务。在核心线程数未满的情况下,新的任务会直接创建新线程来执行。当核心线程数已满时,任务会被放入队列中等待执行。ArrayBlockingQueue(数组阻塞队列):
ArrayBlockingQueue 是一个基于数组的有界阻塞队列,需要指定队列的容量。在核心线程数未满的情况下,新的任务会直接创建新线程来执行。当核心线程数已满时,任务会被放入队列中等待执行。PriorityBlockingQueue(优先级阻塞队列):
PriorityBlockingQueue 是一个无界阻塞队列,它会根据任务的优先级来进行调度。具有较高优先级的任务会被优先执行。DelayedWorkQueue(延迟工作队列):
DelayedWorkQueue 是一个用于调度延迟任务的队列,其中的任务可以设置延迟执行时间。适用于需要按照一定延迟执行任务的场景。SynchronousQueue(同步队列):
SynchronousQueue 是一个没有实际存储能力的队列,每个插入操作必须等待一个相应的删除操作,反之亦然。适用于需要实现一对一的任务交付机制。

用了无界队列那非核心线程就不会创建了?

是的,当线程池使用无界队列(如 LinkedBlockingQueue 或 PriorityBlockingQueue)时,非核心线程的创建将受到影响。在无界队列中,任务可以无限制地排队等待执行,因此不会触发创建额外的非核心线程。

对于无界队列,以下是一些关键点需要注意:

核心线程数 (corePoolSize): 核心线程数仍然会影响线程池的初始线程创建,当任务提交到线程池时,如果当前活动线程数小于核心线程数,新任务会创建一个核心线程来执行。这适用于线程池的初始阶段或在任务数量较少的情况下。最大线程数 (maximumPoolSize): 如果任务的到达速率超过了核心线程的执行速度,并且任务队列已满,非核心线程将不会创建,因为无界队列可以容纳所有任务。任务队列: 无界队列会持续地存储等待执行的任务,直到系统资源用尽。这可能导致内存占用逐渐增加,因此在选择无界队列时需要注意系统资源的管理。

无界队列适用于一些特定场景,如任务量波动大,任务执行时间差异较大,或者希望尽量保留任务而不丢失的情况。然而,需要注意的是,如果任务数量持续增加,无界队列可能会导致内存消耗过大,因此在选择队列类型时需要综合考虑线程池的整体性能和资源利用。

阻塞队列?

前面提到的 LinkedBlockingQueue、PriorityBlockingQueue 和 ArrayBlockingQueue 都是阻塞队列,这里我将更详细地解释一下阻塞队列的概念以及它们的作用。

阻塞队列是一种特殊类型的队列,具有以下特点:

阻塞特性: 当向队列添加元素或从队列中取出元素时,如果队列已满或为空,阻塞队列会自动阻塞线程,直到队列变为非满或非空为止。线程安全: 阻塞队列是线程安全的,多个线程可以并发地进行入队和出队操作,而不需要额外的同步措施。

在 ThreadPoolExecutor 中,任务队列是一个关键组件,它决定了线程池中等待执行的任务数量、调度策略以及如何处理任务。不同类型的阻塞队列在不同的情况下有不同的用途和特点,可以根据实际需求进行选择。

具体来说:

LinkedBlockingQueue 是一个基于链表的无界阻塞队列。当任务数量超过核心线程数时,新的任务会被放入队列中等待执行。如果队列已满,新任务会阻塞等待直到有空间。
ArrayBlockingQueue 是一个基于数组的有界阻塞队列。它需要指定队列的容量。当队列已满时,新的任务会阻塞等待直到有空间。
PriorityBlockingQueue 是一个无界阻塞队列,根据元素的优先级来进行调度。优先级高的元素会被先出队执行。

请注意,阻塞队列适用于不同的场景和需求。根据应用特性和性能要求,选择适合的阻塞队列类型是很重要的。

LinkedBlockingQueue无界队列?

LinkedBlockingQueue 是一个基于链表的可选界限阻塞队列,而不是无界队列。这意味着它可以选择性地指定队列的容量,当容量未指定时,队列会默认为无界。

所以,当使用 LinkedBlockingQueue 作为任务队列时,如果没有指定容量(或者容量为 Integer.MAX_VALUE),队列会被认为是无界的,新任务总是可以放入队列中,而不会因为队列已满而阻塞。

如果指定了容量,当任务数量超过容量时,新的任务会被放入队列中等待执行。当队列已满时,新任务会阻塞等待直到有空间,这是典型的阻塞队列行为。

4、线程工厂:

ThreadPoolExecutor 允许通过提供线程工厂来自定义线程的创建过程,包括线程的名称、优先级、是否守护线程等属性。线程工厂负责创建新的线程实例,然后线程池会使用这些线程来执行任务。

ThreadPoolExecutor 的构造函数中有一个参数 threadFactory,可以传递一个实现了 ThreadFactory 接口的对象来指定线程工厂。ThreadFactory 接口只有一个方法 newThread,用于创建新的线程。以下是一个简单的示例:

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;public class CustomThreadFactoryExample {public static void main(String[] args) {ThreadFactory threadFactory = new CustomThreadFactory("MyThreadGroup");ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS,new LinkedBlockingQueue<>(), threadFactory);// ... 添加任务到线程池并执行 ...executor.shutdown();}
}class CustomThreadFactory implements ThreadFactory {private final ThreadGroup threadGroup;private final AtomicInteger threadNumber = new AtomicInteger(1);public CustomThreadFactory(String threadGroupName) {SecurityManager s = System.getSecurityManager();threadGroup = (s != null) ? s.getThreadGroup() :Thread.currentThread().getThreadGroup();}public Thread newThread(Runnable r) {Thread thread = new Thread(threadGroup, r,"MyThread-" + threadNumber.getAndIncrement(),0);if (thread.isDaemon())thread.setDaemon(false);if (thread.getPriority() != Thread.NORM_PRIORITY)thread.setPriority(Thread.NORM_PRIORITY);return thread;}
}

在上面的示例中,创建了一个自定义的线程工厂 CustomThreadFactory 实现了 ThreadFactory 接口,用于创建具有自定义属性的线程。在这个例子中,指定了线程组、线程名称前缀等属性。

通过使用线程工厂,可以为线程池中的每个线程设置特定的属性,从而更好地管理和控制线程的行为。这对于在多线程应用程序中调试和监视线程非常有帮助。

5、饱和策略:

当线程池和任务队列都已满,新的任务到达时,饱和策略定义了如何处理这些任务。ThreadPoolExecutor 提供了几种内置的饱和策略,如 AbortPolicy(抛出异常)、CallerRunsPolicy(由调用者线程执行任务)、DiscardPolicy(丢弃任务)以及 DiscardOldestPolicy(丢弃最旧的任务)。
下面对每种策略进行详细解释:

AbortPolicy(抛出异常):
这是默认的饱和策略。当线程池和队列都已满时,新任务会导致 RejectedExecutionException 异常被抛出,提示线程池已经饱和。这是一种保守的策略,防止任务丢失。CallerRunsPolicy(由调用者线程执行任务):
当线程池和队列都已满时,新任务会被调用者线程(提交任务的线程)直接执行,而不会交给线程池中的线程来执行。这种策略可能会导致调用者线程阻塞,因为它们会等待任务执行完毕。DiscardPolicy(丢弃任务):
当线程池和队列都已满时,新任务会被直接丢弃,不会进行任何处理。这可能会导致任务丢失,慎用此策略。DiscardOldestPolicy(丢弃最旧的任务):
当线程池和队列都已满时,会尝试将最早的任务从队列中移除,然后添加新任务。这可能会导致一些旧任务被丢弃,以便为新任务腾出空间。

这些饱和策略提供了不同的处理方式,可以根据应用需求来选择合适的策略。通常情况下,AbortPolicy 是默认的且安全的选择,因为它会在资源不足时抛出异常,提示应该考虑调整线程池大小或处理任务队列。其他策略可能会导致任务丢失或阻塞,需要根据具体情况谨慎选择。

6、线程池的执行过程:

当任务提交给 ThreadPoolExecutor 后,线程池会根据当前状态、核心线程数、任务队列状态等决定任务的处理方式。如果核心线程数未满,会创建新线程来执行任务;如果核心线程数已满,会尝试将任务放入队列,如果队列也已满,会根据饱和策略来处理任务。

描述的流程如下:

核心线程数未满:
如果线程池的当前活动线程数小于核心线程数,线程池会创建一个新的核心线程来立即执行提交的任务。核心线程数已满:
如果线程池的当前活动线程数达到核心线程数,新任务会被放入任务队列等待执行。队列未满:
如果任务队列未满,新任务会被放入队列中等待执行。队列已满:
如果任务队列已满,根据选择的饱和策略来处理任务。可能的策略包括:抛出异常(AbortPolicy):如果饱和策略为 AbortPolicy,则新任务会被拒绝,并抛出 RejectedExecutionException 异常。由调用者线程执行(CallerRunsPolicy):如果饱和策略为 CallerRunsPolicy,则提交任务的线程(调用者线程)会执行该任务,而不会交给线程池中的线程执行。丢弃任务(DiscardPolicy):如果饱和策略为 DiscardPolicy,则新任务会被直接丢弃,不会进行任何处理。丢弃最旧的任务(DiscardOldestPolicy):如果饱和策略为 DiscardOldestPolicy,则尝试从队列中移除最旧的任务,以便为新任务腾出空间。

线程池根据这些步骤来动态调整线程的创建和任务的处理,以适应不同的并发情况和资源限制。这种灵活的处理方式使得线程池能够在不同的负载下保持高效的任务处理能力。

7、线程池的终止:

调用 shutdown 方法会触发线程池的终止过程。线程池会拒绝新的任务,等待已提交但未执行的任务完成,然后关闭线程池中的线程。

调用 shutdown 方法是线程池的一种优雅关闭方式。下面将更详细地解释 shutdown 方法的作用和线程池的终止过程:

调用 shutdown 方法:
当调用线程池的 shutdown 方法时,线程池会开始终止的过程。在此过程中,线程池将不再接受新的任务,但会继续执行已提交但尚未执行的任务,同时等待队列中的任务也会被继续执行。任务执行和队列处理:
在终止过程中,线程池会让已经创建的核心线程和非核心线程继续处理已提交的任务。同时,线程池也会尝试从任务队列中获取任务来执行。如果队列中还有等待执行的任务,线程池会继续分配线程来执行这些任务。拒绝新任务:
在调用 shutdown 方法后,线程池会拒绝接受新的任务。任何尝试提交新任务的操作都会被拒绝,并且会抛出 RejectedExecutionException 异常。等待任务完成:
在终止过程中,线程池会等待队列中的任务和正在执行的任务都完成。这意味着线程池不会立即关闭,而是会等待任务全部执行完毕。关闭线程池中的线程:
一旦所有任务都执行完毕,线程池会关闭其中的线程。如果线程池中存在非核心线程,它们在任务执行完毕后会根据 keepAliveTime 和空闲时间来判断是否终止。终止状态:
当线程池中的所有线程都已关闭时,线程池会达到 TERMINATED 状态,表示线程池已经完全终止。

总之,调用 shutdown 方法后,线程池会等待已提交但未执行的任务完成,并且关闭线程池中的线程,最终达到终止状态。这种方式可以避免任务丢失,并且允许线程池逐步优雅地停止,释放资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/37180.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【新品发布】ChatWork企业知识库系统源码

系统简介 基于前后端分离架构以及Vue3、uni-app、ThinkPHP6.x、PostgreSQL、pgvector技术栈开发&#xff0c;包含PC端、H5端。 ChatWork支持问答式和文档式知识库&#xff0c;能够导入txt、doc、docx、pdf、md等多种格式文档。 导入数据完成向量化训练后&#xff0c;用户提问…

两个pdf合并成一个pdf怎么合并?这几个方法值得推荐

两个pdf合并成一个pdf怎么合并&#xff1f;pdf文件的合并是一个很常见的需求&#xff0c;特别是在处理工作文件或学习资料时。为了更好的帮助你了解如何将两个pdf文件合并成一个&#xff0c;下面就给大家详细介绍几种合并方法。 方法一&#xff1a;使用迅捷PDF转换器 这是一款…

小红书如何打造爆款引流吸粉?11个秘诀助你秒变达人!

在这个充满信息和内容的时代&#xff0c;小红书以其独特的社交平台特性和个性化内容吸引了众多用户。今天&#xff0c;我们就来揭秘小红书关注战略&#xff0c;了解如何在这个平台上打造独特的内容体验&#xff0c;与用户建立更亲近的连接。#小红书# 1、定位清晰&#xff0c;找…

【论文阅读】基于深度学习的时序预测——Pyraformer

系列文章链接 论文一&#xff1a;2020 Informer&#xff1a;长时序数据预测 论文二&#xff1a;2021 Autoformer&#xff1a;长序列数据预测 论文三&#xff1a;2022 FEDformer&#xff1a;长序列数据预测 论文四&#xff1a;2022 Non-Stationary Transformers&#xff1a;非平…

Python技巧----解压序列/可迭代对象赋值给多个变量

1 、解压序列赋值给多个变量 我们这里说的不是正常情况的一一赋值比如下面 >>> data = [ ACME, 5, 9, (2012, 12, 1) ] >>> name, shares, price, date = data >>> name ACME

页面跳转和两个页面之间的数据传递-鸿蒙ArkTS

页面跳转和两个页面之间的数据传递-ArkTS 页面跳转和两个页面之间的数据传递-ArkTS关于router的使用**跳转页面的实现方式。**页面接受跳转传递的参数页面返回及携带参数效果代码Index页面Second页面 参考资料 页面跳转和两个页面之间的数据传递-ArkTS 本篇文章主要是对两个页面…

TiDB在科捷物流神州金库核心系统的应用与实践

业务背景 北京科捷物流有限公司于2003年在北京正式成立&#xff0c;是ISO质量管理体系认证企业、国家AAAAA级物流企业、海关AEO高级认证企业&#xff0c;注册资金1亿元&#xff0c;是中国领先的大数据科技公司——神州控股的全资子公司。科捷物流融合B2B和B2C的客户需求&#…

网易有道押宝大模型,打响智能硬件突围战

本文转载自产业科技 自今年开年以来&#xff0c;AI大模型这场火势能不减&#xff0c;如今已燃到教育领域。 7月26日&#xff0c;网易有道举办了“powered by子曰”教育大模型应用成果发布会&#xff0c;推出国内首个教育领域垂直大模型“子曰”&#xff0c;并一口气发布了基于…

conda - 调研介绍

介绍: conda 是一个工具, 也是一个可执行命令, 其核心功能是管理包与环境. conda 支持多种语言, 用来管理Python包是绰绰有余的. 这里注意区分conda和pip, pip命令可以在任何环境中安装Python包, 而conda则是在conda环境中安装任何语言包. 接触过的conda主要有miniconda与anac…

matlab使用教程(15)—图论基础

1.有向图和无向图 1.1什么是图&#xff1f; 图是表示各种关系的节点和边的集合&#xff1a; • 节点 是与对象对应的顶点。 • 边 是对象之间的连接。 • 图的边有时会有权重 &#xff0c;表示节点之间的每个连接的强度&#xff08;或一些其他属性&#xff09;。 这些定…

MySQL8.xx一主两从复制安装与配置

搭建环境: 查看系统版本cat /etc/redhat-release [rootwww tools]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) 查看内核版本cat /proc/version 目标: 一主两从 主机IP 主机名称 端口 搭建环境 安装目录192.168.1.100 docker…

MobaXterm sftp 不能拖拽文件夹了?

原因是我把mobaxterm设置成Windows管理员权限运行了,结果就不能拖动文件。把管理员权限去掉就恢复正常了。 原因是我把mobaxterm设置成Windows管理员权限运行了,结果就不能拖动文件。把管理员权限去掉就恢复正常了。 原因是我把mobaxterm设置成Windows管理员权限运行了,结果就不…

19.正则表达式

19.1什么是正则表达式 ●正则表达式( Regular Expression) 是用于匹配字符串中字符组合的模式。在JavaScript中&#xff0c; 正则表达式也是对象 ●通常用来查找、替换那些符合正则表达式的文本&#xff0c;许多语言都支持正则表达式 ●正则表达式在JavaScript中的使用场景: …

什么是MCU芯片?分类有哪些?与MPU、SoC的区别

1. MCU芯片 MCU&#xff0c;全称为微控制单元&#xff0c;可以看作是CPU频率和规格的缩减。它整合了计数器、内存、USB和A/D转换等功能&#xff0c;形成了一个芯片级的计算机。MCU的重要性仅次于CPU&#xff0c;广泛应用于各种应用场景&#xff0c;如校园卡、身份证、家用电器…

《golang设计模式》第二部分·结构型模式-02-桥接模式(Bridge)

文章目录 1. 概念1.1 角色1.2 类图 2. 代码示例2.1 设计2.1 代码2.2 类图 1. 概念 客户端调用桥接接口实现原有功能和扩展功能的组合 1.1 角色 Implementor&#xff08;实施者&#xff09;&#xff1a; 具体实施者的抽象&#xff0c;可以是一个接口。 Concrete Implementor&…

8.15号经典模型复习笔记

文章目录 Deep Residual Learning for Image Recognition(CVPR2016)方法 Densely Connected Convolutional Networks&#xff08;CVPR2017&#xff09;方法 EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks&#xff08;ICML2019&#xff09;方法 Re…

使用维纳过滤器消除驾驶舱噪音(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

信息论、推理和机器学习算法之间交叉的经典例子

信息论、推理和机器学习算法之间交叉的经典例子: 熵和信息增益在决策树学习中的应用。信息增益利用熵的概念来评估特征的分类能力,从而指导决策树的增长。 交叉熵在神经网络训练中的广泛使用。它结合信息论与最大似然推断,用于度量预测分布与真实分布之间的距离。 变分推断常被…

Tomcat的多实例和动静分离

目录 一、多实例 二、 nginxtomcat的负载均衡和动静分离 三、Tomcat 客户端->四层代理->七层代理->tomcat服务器 实验&#xff1a; 问题总结&#xff1a; tomcat日志文件&#xff1a;/usr/local/tomcat/logs/catalina.out 一、多实例 在一台服务器上有多个tomc…

微信小程序(原生)和uniapp预览电子文件doc/pdf/ppt/excel等

微信小程序原生预览文件 function previewFile(value) {const fileExtName ${value.ext};const randFile new Date().getTime() fileExtName;uni.showLoading({title: 加载中...})wx.downloadFile({url: value.url, // 文件的本身urlfilePath: wx.env.USER_DATA_PATH / r…