【Java异步编程】基于任务类型创建不同的线程池

文章目录

    • 一. 按照任务类型对线程池进行分类
      • 1. IO密集型任务的线程数
      • 2. CPU密集型任务的线程数
      • 3. 混合型任务的线程数
    • 二. 线程数越多越好吗
    • 三. Redis 单线程的高效性

使用线程池的好处主要有以下三点:

  1. 降低资源消耗:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,通过重复利用已创建的线程可以降低线程创建和销毁造成的消耗。
  2. 提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
  3. 提高线程的可管理性:线程池提供了一种限制、管理资源的策略,维护一些基本的线程统计信息,如已完成任务的数量等。通过线程池可以对线程资源进行统一的分配、监控和调优。

虽然使用线程池的好处很多,但是如果其线程数配置得不合理,不仅可能达不到预期效果,反而可能降低应用的性能。接下来按照不同的任务类型来配置线程池。

 

一. 按照任务类型对线程池进行分类

使用标准构造器ThreadPoolExecutor创建线程池时,会涉及线程数的配置,而线程数的配置与异步任务类型是分不开的。这里将线程池的异步任务大致分为以下三类:

  1. IO密集型任务此类任务主要是执行IO操作。由于执行IO操作的时间较长,导致CPU的利用率不高,这类任务CPU常处于空闲状态。Netty的IO读写操作为此类任务的典型例子。
  2. CPU密集型任务此类任务主要是执行计算任务。由于响应时间很快,CPU一直在运行,这种任务CPU的利用率很高。
  3. 混合型任务此类任务既要执行逻辑计算,又要进行IO操作(如RPC调用、数据库访问)​。

相对来说,由于执行IO操作的耗时较长(一次网络往返往往在数百毫秒级别)​,这类任务的CPU利用率也不是太高。Web服务器的HTTP请求处理操作为此类任务的典型例子。一般情况下,针对以上不同类型的异步任务需要创建不同类型的线程池,并进行针对性的参数配置。

 

1. IO密集型任务的线程数

由于IO密集型任务的CPU使用率较低,导致线程空余时间很多,因此通常需要开CPU核心数两倍的线程。当IO线程空闲时,可以启用其他线程继续使用CPU,以提高CPU的使用率。

@Slf4j  
//懒汉式单例创建线程池:用于IO密集型任务  
public class IoIntenseTargetThreadPoolLazyHolder {  /**  * IO线程池最大线程数  */  public static final int IO_MAX = Math.max(2, CPU_COUNT * 2);  /**  * 空闲保活时限,单位秒  */  public static final int KEEP_ALIVE_SECONDS = 30;  /**  * 有界队列size  */    public static final int QUEUE_SIZE = 10000;  //线程池: 用于IO密集型任务  public static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(  IO_MAX,  IO_MAX,  KEEP_ALIVE_SECONDS,  TimeUnit.SECONDS,  new LinkedBlockingQueue(QUEUE_SIZE),  new ThreadUtil.CustomThreadFactory("io"));  public static ThreadPoolExecutor getInnerExecutor() {  return EXECUTOR;  }  static {  log.info("线程池已经初始化");  EXECUTOR.allowCoreThreadTimeOut(true);  //JVM关闭时的钩子函数  Runtime.getRuntime().addShutdownHook(  new ShutdownHookThread("IO密集型任务线程池", new Callable<Void>() {  @Override  public Void call() throws Exception {  //优雅关闭线程池  shutdownThreadPoolGracefully(EXECUTOR);  return null;  }  }));  }  
}

 

有以下几点需要注意

  1. 调用allowCoreThreadTimeOut,传入了参数true,应用于核心线程,当池中的线程长时间空闲时,可以自行销毁。
  2. 使用有界队列缓冲任务而不是无界队列,如果128太小,可以根据具体需要进行增大,但是不能使用无界队列。
  3. corePoolSize和maximumPoolSize保持一致,使得在接收到新任务时,如果没有空闲工作线程,就优先创建新的线程去执行新任务,而不是优先加入阻塞队列,等待现有工作线程空闲后再执行。
  4. 使用JVM关闭时的钩子函数优雅地自动关闭线程池。

 

2. CPU密集型任务的线程数

CPU密集型任务也叫计算密集型任务,其特点是要进行大量计算而需要消耗CPU资源,比如计算圆周率、对视频进行高清解码等。

CPU密集型任务虽然也可以并行完成,但是并行的任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以要最高效地利用CPU,CPU密集型任务并行执行的数量应当等于CPU的核心数。

/**  * CPU核数  **/  
public static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();  public static final int MAXIMUM_POOL_SIZE = CPU_COUNT;  //线程池: 用于CPU密集型任务  
private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(  MAXIMUM_POOL_SIZE,  MAXIMUM_POOL_SIZE,  KEEP_ALIVE_SECONDS,  TimeUnit.SECONDS,  new LinkedBlockingQueue(QUEUE_SIZE),  new CustomThreadFactory("cpu"));  public static ThreadPoolExecutor getInnerExecutor() {  return EXECUTOR;  
}  static {  log.info("线程池已经初始化");  EXECUTOR.allowCoreThreadTimeOut(true);  //JVM关闭时的钩子函数  Runtime.getRuntime().addShutdownHook(  new ShutdownHookThread("CPU密集型任务线程池", new Callable<Void>() {  @Override  public Void call() throws Exception {  //优雅关闭线程池  shutdownThreadPoolGracefully(EXECUTOR);  return null;  }  }));  
}

 

3. 混合型任务的线程数

混合型任务既要执行逻辑计算,又要进行大量非CPU耗时操作(如RPC调用、数据库访问、网络通信等)​,所以混合型任务CPU的利用率不是太高,非CPU耗时往往是CPU耗时的数倍

比如在Web应用中处理HTTP请求时,一次请求处理会包括DB操作、RPC操作、缓存操作等多种耗时操作。一般来说,一次Web请求的CPU计算耗时往往较少,大致在100~500毫秒,而其他耗时操作会占用500~1000毫秒,甚至更多的时间。

在为混合型任务创建线程池时,如何确定线程数呢?业界有一个比较成熟的估算公式,具体如下:


最佳线程数 = ((线程等待时间+线程CPU时间) / 线程CPU时间) * CPU核数

通过公式可以看出:等待时间所占的比例越高,需要的线程就越多;CPU耗时所占的比例越高,需要的线程就越少

 

下面举一个例子:

比如在Web服务器处理HTTP请求时,假设平均线程CPU运行时间为100毫秒,而线程等待时间(比如包括DB操作、RPC操作、缓存操作等)为900毫秒,如果CPU核数为8,那么根据上面这个公式,估算如下:

900毫秒 + 100毫秒) / 100毫秒 * 8 = 10 * 8 = 80

 

二. 线程数越多越好吗

很多小伙伴认为,线程数越高越好。那么,使用很多线程是否就一定比单线程高效呢?答案是否定的。

虽然多线程在一些并发场景下能带来性能提升,但过多的线程并不意味着性能必定提升。线程数过高可能导致一些问题:

  • 上下文切换(Context Switching): 每个线程的执行都由操作系统调度,线程切换会带来额外的开销。当线程数过多时,操作系统频繁地在不同线程间切换,导致 上下文切换 成本增加,这样反而可能降低系统的整体效率。

  • 资源争用: 多线程同时访问共享资源时,可能会遇到 资源竞争锁竞争,特别是在 CPU 绑定的任务中。线程之间的协作和同步会称为性能瓶颈。

  • 内存开销: 每个线程需要占用一定的内存,维护线程栈、调度信息等,过多的线程会消耗大量的内存和系统资源,这可能会导致系统性能下降,甚至造成内存溢出

 

三. Redis 单线程的高效性

Redis 是一个 单线程 的高性能数据库,许多人可能会觉得它的设计不合常理,为什么不使用多线程来提升性能呢?然而,Redis 使用单线程反而能够达到极高的吞吐量,这是因为:

特点核心内容
1. 避免多线程上下文切换单线程模型避免了线程切换的开销,任务按顺序处理,简化了并发控制,避免了锁竞争和死锁问题。
2. 非阻塞设计采用事件驱动和 I/O 多路复用技术,非阻塞处理请求。如果一个请求需要等待外部资源(如网络 I/O),Redis 会把控制权交给其他请求,而不是阻塞线程。这种方式避免了多线程中因为等待 I/O 资源导致的线程空闲,充分利用了 CPU 的时间片。
3. CPU vs I/O 密集型Redis 的大多数操作(如 GET/SET)是 I/O 密集型 的,单线程在 I/O 密集型应用中有优势。
4. 数据访问模式Redis 操作主要是内存访问,内存操作速度快,单线程执行时没有同步问题,数据结构(如哈希表、跳表等)高效。

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

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

相关文章

14-9-2C++STL的set容器

&#xff08;一&#xff09;函数对象的基本概念 set容器的元素排序 1.set<int,less<int> >setlntA;//该容器是按升序方式排列元素&#xff0c;set<int>相当于set<int,less<int>> 2.set<int,greater<int> >setlntB;//该容器是按降序…

音视频入门基础:RTP专题(8)——使用Wireshark分析RTP

一、引言 通过Wireshark可以抓取RTP数据包&#xff0c;该软件可以从Wireshark Go Deep 下载。 二、通过Wireshark抓取RTP数据包 首先通过FFmpeg将一个媒体文件转推RTP&#xff0c;生成RTP流&#xff1a; ffmpeg -re -stream_loop -1 -i input.mp4 -vcodec copy -an -f rtp …

tf.Keras (tf-1.15)使用记录3-model.compile方法

model.compile 是 TensorFlow Keras 中用于配置训练模型的方法。在开始训练之前&#xff0c;需要通过这个方法来指定模型的优化器、损失函数和评估指标等。 注意事项: 在开始训练&#xff08;调用 model.fit&#xff09;之前&#xff0c;必须先调用 model.compile()。 1 基本…

解决whisper 本地运行时GPU 利用率不高的问题

我在windows 环境下本地运行whisper 模型&#xff0c;使用的是nivdia RTX4070 显卡&#xff0c;结果发现GPU 的利用率只有2% 。使用 import torch print(torch.cuda.is_available()) 返回TRUE。表示我的cuda 是可用的。 最后在github 的下列网页上找到了问题 极低的 GPU 利…

大模型综合性能考题汇总

- K1.5长思考版本 一、创意写作能力 题目1&#xff1a;老爸笑话 要求&#xff1a;写五个原创的老爸笑话。 考察点&#xff1a;考察模型的幽默感和创意能力&#xff0c;以及对“原创”要求的理解和执行能力。 题目2&#xff1a;创意故事 要求&#xff1a;写一篇关于亚伯拉罕…

在 crag 中用 LangGraph 进行评分知识精炼-下

在上一次给大家展示了基本的 Rag 检索过程&#xff0c;着重描述了增强检索中的知识精炼和补充检索&#xff0c;这些都是 crag 的一部分&#xff0c;这篇内容结合 langgraph 给大家展示通过检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;的工作流&am…

(二)QT——按钮小程序

目录 前言 按钮小程序 1、步骤 2、代码示例 3、多个按钮 ①信号与槽的一对一 ②多对一&#xff08;多个信号连接到同一个槽&#xff09; ③一对多&#xff08;一个信号连接到多个槽&#xff09; 结论 前言 按钮小程序 Qt 按钮程序通常包含 三个核心文件&#xff1a; m…

win11本地部署 DeepSeek-R1 大模型!免费开源,媲美OpenAI-o1能力,断网也能用

一、下载ollama 二、安装ollama 三、部署DeepSeek-R1 在cmd窗口中先输入ollama -v查看ollama是否安装成功&#xff0c;然后直接运行部署deepseek-r1的命令 ollama run deepseek-r1&#xff0c;出现下面界面即为安装成功。 C:\Users\admin>ollama -v ollama version is 0.5…

蓝桥杯例题六

奋斗是一种态度&#xff0c;也是一种生活方式。无论我们面对什么样的困难和挑战&#xff0c;只要心怀梦想&#xff0c;坚持不懈地努力&#xff0c;就一定能够迈向成功的道路。每一次失败都是一次宝贵的经验&#xff0c;每一次挫折都是一次锻炼的机会。在困难面前&#xff0c;我…

【工欲善其事】利用 DeepSeek 实现复杂 Git 操作:从原项目剥离出子版本树并同步到新的代码库中

文章目录 利用 DeepSeek 实现复杂 Git 操作1 背景介绍2 需求描述3 思路分析4 实现过程4.1 第一次需求确认4.2 第二次需求确认4.3 第三次需求确认4.4 V3 模型&#xff1a;中间结果的处理4.5 方案验证&#xff0c;首战告捷 5 总结复盘 利用 DeepSeek 实现复杂 Git 操作 1 背景介绍…

B+ 树的实现原理与应用场景

B 树是如何实现的全面分析 在进行数据库和文件系统的设计中&#xff0c;B 树是一种常用的数据结构。它不仅是 B 树的延伸&#xff0c;而且团结了性能优化和实现上的优势。本文将从学术理论和实现程序的角度&#xff0c;分析 B 树是如何实现的&#xff0c;以及它依赖于哪些具体…

TensorFlow 示例摄氏度到华氏度的转换(一)

TensorFlow 实现神经网络模型来进行摄氏度到华氏度的转换&#xff0c;可以将其作为一个回归问题来处理。我们可以通过神经网络来拟合这个简单的转换公式。 1. 数据准备与预处理 2. 构建模型 3. 编译模型 4. 训练模型 5. 评估模型 6. 模型应用与预测 7. 保存与加载模型 …

gitea - fatal: Authentication failed

文章目录 gitea - fatal: Authentication failed概述run_gitea_on_my_pkm.bat 笔记删除windows凭证管理器中对应的url认证凭证启动gitea服务端的命令行正常用 TortoiseGit 提交代码备注END gitea - fatal: Authentication failed 概述 本地的git归档服务端使用gitea. 原来的用…

【深度解析】DeepSeek-R1的五大隐藏提示词

LangChain系列文章目录 01-玩转LangChain&#xff1a;从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块&#xff1a;四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain&#xff1a;从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…

基于RAG的知识库问答系统

基于RAG的知识库问答系统 结合语义检索与大语言模型技术&#xff0c;实现基于私有知识库的智能问答解决方案。采用两阶段处理架构&#xff0c;可快速定位相关文档并生成精准回答。 核心功能 知识向量化引擎 支持多语言文本嵌入&#xff08;all-MiniLM-L6-v2模型&#xff09;自…

Windsurf cursor vscode+cline 与Python快速开发指南

Windsurf简介 Windsurf是由Codeium推出的全球首个基于AI Flow范式的智能IDE&#xff0c;它通过强大的AI助手功能&#xff0c;显著提升开发效率。Windsurf集成了先进的代码补全、智能重构、代码生成等功能&#xff0c;特别适合Python开发者使用。 Python环境配置 1. Conda安装…

MySql运维篇---008:日志:错误日志、二进制日志、查询日志、慢查询日志,主从复制:概述 虚拟机更改ip注意事项

#先登录mysql mysql -uroot -p1234#通过此系统变量&#xff0c;查看当前mysql的版本中默认的日志格式是哪个 show variables like %binlog\_format%;1.2.3 查看 由于日志是以二进制方式存储的&#xff0c;不能直接读取&#xff0c;需要通过二进制日志查询工具 mysqlbinlog 来查…

踏入编程世界的第一个博客

我&#xff0c;一个双非一本大一新生&#xff0c;普通的不能再普通了&#xff0c;面对宏伟庞大的计算机世界仍显得举手无措&#xff0c;我自以为自身仍有些许骨气&#xff0c;不想普普通通&#xff0c;甚是浑浑噩噩的度过四年大学&#xff0c;经历了高考的打击&#xff0c;双非…

【背包问题】二维费用的背包问题

目录 二维费用的背包问题详解 总结&#xff1a; 空间优化&#xff1a; 1. 状态定义 2. 状态转移方程 3. 初始化 4. 遍历顺序 5. 时间复杂度 例题 1&#xff0c;一和零 2&#xff0c;盈利计划 二维费用的背包问题详解 前面讲到的01背包中&#xff0c;对物品的限定条件…

使用 DeepSeek-R1 等推理模型将 RAG 转换为 RAT,以实现更智能的 AI

使用 DeepSeek-R1 等推理模型将 RAG 转换为 RAT&#xff0c;以实现更智能的 AI 传统的检索增强生成&#xff08;RAG&#xff09;系统在生成具备上下文感知的答案方面表现出色。然而&#xff0c;它们往往存在以下不足&#xff1a; 精确性不足&#xff1a;单次推理可能会忽略复杂…