当前位置: 首页 > news >正文

线程调优——调整线程池参数提升程序执行效率

        先抛出一个问题,程序开发真的是线程越多效率越高吗?多线程是我们程序开发中必不可少的手段,线程就像“孙悟空”开启了分身术一样,每个分身都在“打妖怪”,那是不是分身越多,“打妖怪”的效率就越高?

文章目录

    • 一、前言
    • 二、演示实例
    • 三、执行结果
    • 四、结论分析

一、前言

        其实,线程的数量并不是越多越好,每个线程都需要系统分配一定的资源,如内存和CPU时间。每个线程都有其栈内存,线程数量过多可能导致内存不足,甚至可能影响系统的稳定性。线程的管理和使用需要根据具体的应用场景和需求来决定,通常,线程数量应与可用的CPU核心数相匹配,或者稍微多一些,以充分利用多核处理器的能力。如果线程数量超过了CPU核心数,线程间的上下文切换会变得频繁,可能会导致性能下降。
        一般情况下,根据业务的需求及服务器的配置,调整最优的线程池参数来进行线程调优,提升程序的执行效率。说线程池调优之前,先说下线程池的4个重要参数,分别是:corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、workQueue(队列容量)、keepAliveTime(非核心回收超时时间)。

corePoolSize:是线程池中保持活动状态的线程的最小数量,即使这些线程在空闲时也不会被终止。

maximumPoolSize:是线程池能够容纳的最大线程数量,限制线程池中线程的最大数量,防止过多线程的创建,避免资源耗尽和系统过载。

workQueue:是一个阻塞队列,用于存储待执行的任务。

keepAliveTime:线程存活时间,是线程池中多余线程(即除过corePoolSize之外的线程)在空闲时保持活动的最大时间。如果线程在此时间内没有被使用,则会被终止。

以下通过一个demo来演示下线程池中实际参与工作的线程数!

二、演示实例

我们初始化的参数分别是corePoolSize=10、maximumPoolSize=30、workQueue=50,然后依次模拟5、10、15、45、60、65、75、80、81的任务数,下面的演示模拟的代码:

	public static void main(String[] args) {// 模拟任务数int taskCount = 75;// 使用AtomicInteger线程安全的计数器,支持在多线程环境中安全地增减值而不需要使用锁AtomicInteger integer = new AtomicInteger();// 定义一个时间区间集合,存储各个任务执行到时的耗时(单位:毫秒)情况List<Long> diffList = new ArrayList<>();// 初始化线程池,核心参数如下ThreadPoolExecutor executor = new ThreadPoolExecutor(10, // 核心线程数30, // 最大线程数5, // 非核心回收超时时间TimeUnit.SECONDS, // 超时时间单位new ArrayBlockingQueue<>(50));  // 任务队列// 记录当前时间戳long start = System.currentTimeMillis();// 模拟程序任务for (int i = 0; i < taskCount; i++) {Thread thread = new Thread(() -> {try {// 模拟执行耗时1秒Thread.sleep(1000);// 自增步进1并返回新值int j = integer.addAndGet(1);// 当前任务耗时记录long l = System.currentTimeMillis() - start;System.out.println("已执行" + j + "个任务,耗时" + l + "ms");// 把耗时记录存储到集合中diffList.add(l);} catch (InterruptedException e) {e.printStackTrace();}});// 把线程放到线程池中try {executor.execute(thread);} catch (Exception e) {e.printStackTrace();}}// 验证程序执行结束,并记录结束时间戳long end = 0;while (executor.getCompletedTaskCount() < taskCount) {end = System.currentTimeMillis();}System.out.println("总任务数:"+taskCount);/*** 1.把集合中所有时间区间的值都除以1000,这样的话,集合中的值就只会出现整型的1、2、3...,用于标识任务属于哪个批次(刚好1秒一个批次)* 2.然后把集合聚合分组整理成HashMap,方便我们统计各个批次的数量* 3.然后打印出来*/Map<Long, List<Long>> collect = diffList.stream().map(i -> i / 1000).collect(Collectors.groupingBy(i -> i.longValue()));for (Long l : collect.keySet()) {System.out.println("第【"+l+"】批次,工作线程数:"+collect.get(l).size());}System.out.println("任务总耗时:" + (end - start) + "ms");}

三、执行结果

为了方便对比查看,我把每次的执行结果都贴到一起了,如下:

总任务数:5
第【1】批次,工作线程数:5
任务总耗时:1074ms
-----------------------------------------
总任务数:10
第【1】批次,工作线程数:10
任务总耗时:1069ms
-----------------------------------------
总任务数:15
第【1】批次,工作线程数:10
第【2】批次,工作线程数:5
任务总耗时:2068ms
-----------------------------------------
总任务数:45
第【1】批次,工作线程数:10
第【2】批次,工作线程数:10
第【3】批次,工作线程数:10
第【4】批次,工作线程数:10
第【5】批次,工作线程数:5
任务总耗时:5117ms
-----------------------------------------
总任务数:60
第【1】批次,工作线程数:10
第【2】批次,工作线程数:10
第【3】批次,工作线程数:10
第【4】批次,工作线程数:10
第【5】批次,工作线程数:10
第【6】批次,工作线程数:10
任务总耗时:6151ms
-----------------------------------------
总任务数:65
第【1】批次,工作线程数:15
第【2】批次,工作线程数:15
第【3】批次,工作线程数:15
第【4】批次,工作线程数:15
第【5】批次,工作线程数:5
任务总耗时:5128ms
-----------------------------------------
总任务数:75
第【1】批次,工作线程数:25
第【2】批次,工作线程数:25
第【3】批次,工作线程数:25
任务总耗时:3079ms
-----------------------------------------
总任务数:80
第【1】批次,工作线程数:30
第【2】批次,工作线程数:30
第【3】批次,工作线程数:20
任务总耗时:3079ms
-----------------------------------------
总任务数:81
java.util.concurrent.RejectedExecutionException: Task Thread[Thread-80,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@6b143ee9[Running, pool size = 30, active threads = 30, queued tasks = 50, completed tasks = 0]
-----------------------------------------

四、结论分析

根据上述结果分析,可以得到如下结论:

数量关系工作线程数 task示例
场景1 taskCount <= corePoolSize taskCount 5、10
场景2 corePoolSize < taskCount <= corePoolSize+workQueue corePoolSize 15、45、60
场景3 corePoolSize+workQueue < taskCount <= maximumPoolSize+workQueue taskCount-workQueue 65、75、80
场景4 taskCount > maximumPoolSize+workQueue 小于的部分正常执行,大于的部分会拒绝策略,在线程池中抛出异常 81
字段说明 corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、workQueue(队列容量)、taskCount(任务数)

        所以,不一定任务数量少就耗时少,也不一定任务数量多就耗时长,因为不同任务下线程池分配的工作线程数是不一样的。合理地调配资源和参数,在充分利用服务器资源的同时,可以达到程序执行的最优状态!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • git-fork操作指南
  • Linux6-vi/vim
  • Snowflake的“AI + 数据” 模式,如何颠覆传统数据处理!
  • 掌握 Flutter 中的 `Overlay` 和 `OverlayEntry`:弹窗管理的艺术
  • 大数据Flink(一百二十二):阿里云Flink MySQL连接器介绍
  • 前端在网络安全攻击问题上能做什么?
  • 计算机四级-计算机网络
  • JIT(即时编译)技术
  • mac新手入门(快捷键)
  • Java原生HttpURLConnection实现Get、Post、Put和Delete请求完整工具类分享
  • 《C++中的资源管理利器:RAII 技术深度剖析》
  • C++ 文件操作
  • 牛客小白月赛101(栈、差分、调和级数、滑动窗口)
  • NFT Insider #148:The Sandbox 推出 SHIBUYA Y3K 时尚系列,Azuki 进军动漫 NFT 领域
  • 分享一个通用OCR模型GOT-OCR2.0
  • __proto__ 和 prototype的关系
  • avalon2.2的VM生成过程
  • Git初体验
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • JavaScript异步流程控制的前世今生
  • node-glob通配符
  • Python学习之路13-记分
  • Redis的resp协议
  • Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比...
  • Webpack 4x 之路 ( 四 )
  • 初识MongoDB分片
  • 工作手记之html2canvas使用概述
  • 关于 Cirru Editor 存储格式
  • 检测对象或数组
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 数据科学 第 3 章 11 字符串处理
  • 我感觉这是史上最牛的防sql注入方法类
  • 详解NodeJs流之一
  • 《码出高效》学习笔记与书中错误记录
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • 容器镜像
  • 如何通过报表单元格右键控制报表跳转到不同链接地址 ...
  • 通过调用文摘列表API获取文摘
  • ​io --- 处理流的核心工具​
  • ​queue --- 一个同步的队列类​
  • # Kafka_深入探秘者(2):kafka 生产者
  • ###C语言程序设计-----C语言学习(6)#
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • #git 撤消对文件的更改
  • #ifdef 的技巧用法
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (09)Hive——CTE 公共表达式
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (6)STL算法之转换
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (附源码)基于SSM多源异构数据关联技术构建智能校园-计算机毕设 64366
  • (排序详解之 堆排序)
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?