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

Java避坑案例 - 线程池错误的混用引发的性能故障分析

文章目录

  • 问题现象
  • 问题分析
  • 问题修复
  • 线程池的混用策略
  • 任务类型与线程池配置
  • 最佳实践

在这里插入图片描述


问题现象

代码使用了线程池异步处理一些内存中的数据,但通过监控发现处理得非常慢,整个处理过程都是内存中的计算不涉及 IO 操作,也需要数秒的处理时间,应用程序 CPU 占用也不是特别高,有点不可思议


问题分析

经排查发现,业务代码使用的线程池,除了自身业务使用,同时还被一个后台的文件批处理任务用到了。

 private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 2,1, TimeUnit.HOURS,new ArrayBlockingQueue<>(100),new ThreadFactoryBuilder().setNameFormat("batchfileprocess-threadpool-%d").build(),new ThreadPoolExecutor.CallerRunsPolicy());public int handleFile() throws ExecutionException, InterruptedException {return threadPool.submit(calcTask()).get();}

接下来我们模拟一下文件批处理的代码,在程序启动后通过一个线程开启死循环逻辑,不断向线程池提交任务,任务的逻辑是向一个文件中写入大量的数据

/*** 在Bean初始化完成后执行的方法* 此方法初始化线程池并启动一个新线程来执行特定任务*/
@PostConstruct
public void init() {// 打印线程池的初始状态和统计信息printStats(threadPool);// 创建并启动一个新的线程来执行批处理任务new Thread(() -> {// 生成一个由'a'字符组成的100万字符长的字符串作为负载String payload = IntStream.rangeClosed(1, 1_000_000).mapToObj(__ -> "a").collect(Collectors.joining(""));// 无限循环执行任务while (true) {// 使用线程池执行写入操作threadPool.execute(() -> {try {// 写入当前时间戳和负载到文件中Files.write(Paths.get("artisan.txt"), Collections.singletonList(LocalTime.now().toString() + ":" + payload), UTF_8, CREATE, TRUNCATE_EXISTING);} catch (IOException e) {// 打印异常信息e.printStackTrace();}// 记录日志,表示批处理任务完成log.info("batch file processing done");});}}).start();
}/*** 定期打印线程池的运行统计信息* 此方法内部创建了一个新的单线程调度器,用于定期执行打印线程池统计信息的任务* 它提供了线程池大小、活动线程数、已完成任务数和队列中任务数的信息* 这些信息有助于监控线程池的性能和工作负载* * @param threadPool 线程池对象,其统计信息将被打印*/
private void printStats(ThreadPoolExecutor threadPool) {Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {// 打印分割线,用于区分不同的统计时间点log.info("=========================");// 打印线程池当前的线程数量log.info("Pool Size: {}", threadPool.getPoolSize());// 打印当前活动线程的数量log.info("Active Threads: {}", threadPool.getActiveCount());// 打印已完成任务的总数log.info("Number of Tasks Completed: {}", threadPool.getCompletedTaskCount());// 打印队列中等待执行的任务数量log.info("Number of Tasks in Queue: {}", threadPool.getQueue().size());// 再次打印分割线,结束本次统计信息的打印log.info("=========================");}, 0, 1, TimeUnit.SECONDS);
}

流程如下:

初始化线程池统计信息
启动新线程
生成大字符串 payload
进入无限循环
使用 threadPool 执行任务
写入文件 artisan.txt
记录日志

通过 printStats 方法打印出的日志可以看到,这个线程池中的 2 个线程任务是相当重的。

在这里插入图片描述

  • 线程池的 2 个线程始终处于活跃状态,队列也基本处于打满状态

  • 因为开启了CallerRunsPolicy 拒绝处理策略,所以当线程满载队列也满的情况下,任务会在提交任务的线程,或者说调用 execute 方法的线程执行,也就是说不能认为提交到线程池的任务就一定是异步处理的。如果使用了 CallerRunsPolicy 策略,那么有可能异步任务变为同步执行。从日志的第四行也可以看到这点。这也是这个拒绝策略比较特别的原因


试想一下: 业务代码复用这样的线程池来做内存计算,会怎样???

写一段代码测试下,向线程池提交一个简单的任务,这个任务只是休眠 10 毫秒没有其他逻辑

/*** 创建一个计算任务,该任务在执行时会暂停一段时间然后返回固定的结果* * @return Callable<Integer> 一个计算任务,当运行时会暂停10毫秒,然后返回整数1*/
private Callable<Integer> calcTask() {return () -> {// 暂停10毫秒,模拟耗时操作或等待资源TimeUnit.MILLISECONDS.sleep(10);// 返回固定的结果1,表示任务完成return 1;};
}/*** 错误的异步任务处理示例* * 此方法展示了一个错误的异步任务处理方式,通过提交一个计算任务到线程池并立即获取结果的方式* 这种做法未能正确处理异步任务,因为它会导致线程池的线程阻塞,直到任务完成,从而降低了系统的整体性能和响应速度* * @return 计算任务的结果* @throws ExecutionException 如果计算任务执行失败* @throws InterruptedException 如果线程被中断*/
@GetMapping("wrong")
public int wrong() throws ExecutionException, InterruptedException {return threadPool.submit(calcTask()).get();
}

使用 wrk 工具对这个接口进行一个简单的压测,可以看到 TPS 为 75,性能的确非常差

在这里插入图片描述

细想一下,问题其实没有这么简单。因为 原来执行 IO 任务的线程池使用的是CallerRunsPolicy 策略,所以直接使用这个线程池进行异步计算的话,当线程池饱和的时候,计算任务会在执行 Web 请求的 Tomcat 线程执行,这时就会进一步影响到其他同步处理的线程,甚至造成整个应用程序崩溃


问题修复

解决方案很简单,使用独立的线程池来做这样的“计算任务”即可。计算任务打了双引号,是因为我们的模拟代码执行的是休眠操作,并不属于 CPU 绑定的操作,更类似 IO 绑定的操作,如果线程池线程数设置太小会限制吞吐能力

 /*** 创建一个固定大小的线程池用于异步计算任务* * 线程池的配置参数说明:* - 核心线程数和最大线程数都设置为200,意味着线程池只会创建200个线程来执行任务* - 线程空闲时间设为1小时,即线程在空闲1小时后将被终止* - 使用ArrayBlockingQueue作为任务队列,队列大小设为1000,这意味着在队列满的情况下,*   新提交的任务将等待直到队列中有空位* - 通过ThreadFactoryBuilder设置线程名称格式,以便于追踪和管理线程*/
private static ThreadPoolExecutor asyncCalcThreadPool = new ThreadPoolExecutor(200, 200,1, TimeUnit.HOURS,new ArrayBlockingQueue<>(1000),new ThreadFactoryBuilder().setNameFormat("asynccalc-threadpool-%d").build());/*** 使用异步方式计算并返回结果* 本方法通过提交一个计算任务到异步计算线程池,并等待任务完成后的结果* 如果任务抛出异常,将会被传播到调用者* * @return 计算结果* @throws ExecutionException 如果计算任务执行失败或被取消* @throws InterruptedException 如果等待结果时线程被中断*/
@GetMapping("right")
public int right() throws ExecutionException, InterruptedException {// 提交一个异步计算任务到线程池并获取Future对象,用于获取计算结果return asyncCalcThreadPool.submit(calcTask()).get();
}

使用单独的线程池改造代码后再来测试一下性能,TPS 提高到了 1727

在这里插入图片描述

在这个案例中,使用了一个只包含两个核心线程的线程池,同时被 I/O 密集型和 CPU 密集型任务共享,导致了性能瓶颈。由于采用了 CallerRunsPolicy 拒绝策略,当线程池达到饱和时,任务被回退到调用线程执行,进而影响了整个应用的性能。

这种配置的影响是显而易见的:异步任务的执行可能变成了同步执行,进一步降低了应用的响应能力。


线程池的混用策略

线程池的设计确实是为了复用资源,但这并不意味着所有任务都应共享同一个线程池。不同类型的任务(如 I/O 密集型与 CPU 密集型)对线程池的需求差异显著,因此需要谨慎选择线程池的配置和混用策略。


任务类型与线程池配置

  1. I/O 密集型任务:如网络请求或文件操作,通常需要更多的线程来处理潜在的阻塞,因此可配置较高的核心线程数,且队列大小适中。
  2. CPU 密集型任务:计算密集型任务应该限制线程数量,通常设置为 CPU 核数或 CPU 核数的两倍,以减少线程切换的开销。这类任务可能需要更大的队列来缓冲任务。

最佳实践

  • 任务隔离:为不同类型的任务创建独立的线程池,避免混用。
  • 合理配置:根据任务的特性(I/O 密集或 CPU 密集)合理设置线程池的核心参数。
  • 监控与调优:定期监控线程池的性能指标,根据实际需求进行调整。

在这里插入图片描述

相关文章:

Java避坑案例 - 线程池错误的混用引发的性能故障分析

文章目录 问题现象问题分析问题修复线程池的混用策略任务类型与线程池配置最佳实践 问题现象 代码使用了线程池异步处理一些内存中的数据&#xff0c;但通过监控发现处理得非常慢&#xff0c;整个处理过程都是内存中的计算不涉及 IO 操作&#xff0c;也需要数秒的处理时间&…...

七种方法助你找到实用且免费的API服务

随着现代互联网的迅猛发展&#xff0c;API&#xff08;应用程序编程接口&#xff09;已成为推动技术创新的核心工具。API使得开发者能够快速实现复杂的功能&#xff0c;如数据分析、自然语言处理、图像识别等&#xff0c;而无需从头编写大量的代码。在这个开放的生态中&#xf…...

leetcode-74-搜索二维矩阵

题解&#xff1a; 遍历二维数组matrix中的每个list&#xff0c;如果target在list中则返回True&#xff08;退出循环&#xff09;&#xff1b;如果全部遍历完还没有出现target则返回False。 核心就是在list中查找target&#xff01; 代码实现&#xff1a;...

122.WEB渗透测试-信息收集-ARL(13)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;121.WEB渗透测试-信息收集-ARL&#xff08;12&#xff09; 输入命令&#xff1a; docker…...

动态规划 —— 路径问题-下降路径最小和

1. 下降路径最小和 题目链接&#xff1a; 931. 下降路径最小和 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/minimum-falling-path-sum/description/ 2. 算法原理 状态表示&#xff1a;以莫一个位置位置为结尾 dp[i&#xff0c;j]表示&#xff1a;到…...

【Linux网络】TCP_Socket

目录 TCP协议&#xff08;传输控制协议&#xff09; listen状态 accept和connect TCP_echo_server (1)创建套接字 &#xff08;2&#xff09;绑定 &#xff08;3&#xff09;设置listen状态 &#xff08;4&#xff09;loop &#xff08;5&#xff09;客户端 多线程远程…...

NVR批量管理软件/平台EasyNVR多个NVR同时管理支持视频投放在电视墙上

在当今智能化、数字化的时代&#xff0c;视频监控已经成为各行各业不可或缺的一部分&#xff0c;无论是公共安全、交通管理、企业监控还是智慧城市建设&#xff0c;都离不开高效、稳定的视频监控系统的支持。而在这些应用场景中&#xff0c;将监控视频实时投放到大屏幕电视墙上…...

Springboot集成阿里云通义千问(灵积模型)

我这里集成后&#xff0c;做成了一个工具jar包&#xff0c;如果有不同方式的&#xff0c;欢迎大家讨论&#xff0c;共同进步。 集成限制&#xff1a; 1、灵积模型有QPM(QPS)限制&#xff0c;每个模型不一样&#xff0c;需要根据每个模型适配 集成开发思路&#xff1a; 因有…...

微信公众号(或微信浏览器)获取openId(网页授权)

下单支付需要openId 首先授权去拿到code --然后调用后太换取openId 1.去拿取code 下图中执行到window.location.href &#xff08; redirect_uri 传入当前路径-&#xff09;–执行后重新跳转到当前页面–但是路径上会带上code参数 //然后调用后台方法–将code传给后台得到 o…...

C++算法第五天

本篇文章继续和大家一起刷算法题 第一题 题目链接 . - 力扣&#xff08;LeetCode&#xff09; 题目解析 题目要求&#xff1a; 这是一个连续的子数组 计算子数组内元素的和&#xff0c;若数组内元素的和符合 > target的值并且该子数组的长度是最短的&#xff0c;则返回…...

牛客网剑指Offer-树篇-JZ26 树的子结构

题目 来源&#xff1a;JZ26 树的子结构 描述 输入两棵二叉树A&#xff0c;B&#xff0c;判断B是不是A的子结构。&#xff08;我们约定空树不是任意一个树的子结构&#xff09; 假如给定A为{8,8,7,9,2,#,#,#,#,4,7}&#xff0c;B为{8,9,2}&#xff0c;2个树的结构如下&#xff…...

FFmpeg 4.3 音视频-多路H265监控录放C++开发六,使用SDLVSQT显示yuv文件

使用QT 显示YUV 文件 在最后一帧的时候会不停的显示最后一帧图片。 Vsqtshowyuv.h #pragma once#include <QtWidgets/QWidget> #include "ui_vsqtshowyuv.h" #include <sdl/SDL.h> #include <iostream> #include <fstream> #include <Q…...

Spring 设计模式之适配器模式

Spring 设计模式之适配器模式 适配器模式用到的场景java举例 适配器模式 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许接口不兼容的类一起工作。 其核心思想是通过一个适配器类将不兼容的接口转换成客户端期望的另一个接口&…...

多传感器数字化分析系统

在工业飞速发展的今天&#xff0c;设备的安全稳定运行成为企业高效生产的关键因素。然而&#xff0c;传统的人工巡检方式面临着诸多挑战&#xff0c;如效率低下、漏检误检以及难以精准掌握设备运行状态等。旗晟凭借深厚的技术积累和创新精神&#xff0c;推出了多传感器数字化分…...

Java 基础教学:面向对象编程基础-封装、继承与多态

面向对象编程&#xff08;OOP&#xff09;是现代编程的重要范式&#xff0c;Java 语言提供了丰富的 OOP 特性&#xff0c;主要包括封装、继承和多态。本文将详细讲解这三个概念及其实现方式&#xff0c;并提供相应的代码示例。 1. 封装 1.1 概念 封装是将对象的状态&#xf…...

Ubuntu环境本地部署DbGate数据库管理工具并实现无公网IP远程访问

文章目录 前言1. 安装Docker2. 使用Docker拉取DbGate镜像3. 创建并启动DbGate容器4. 本地连接测试5. 公网远程访问本地DbGate容器5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定公网地址远程访问 前言 本文主要介绍如何在Linux Ubuntu系统中使用Docker部署DbGate数…...

【AI抠图整合包及教程】Meta SAM 2:视觉分割的革命性飞跃

在人工智能的浪潮中&#xff0c;每一次技术的革新都如同一场视觉盛宴&#xff0c;让我们见证着数字时代的变迁。Meta再次以Segment Anything Model 2&#xff08;SAM 2&#xff09;引领了图像和视频分割技术的新纪元。作为首个用于实时、可提示的图像和视频对象分割的统一模型&…...

使用语言模型进行文本摘要的五个级别(llm)

视频链接&#xff1a;5 Levels Of LLM Summarizing: Novice to Expert...

ubuntu交叉编译libffi库给arm平台使用

1.下载并解压&#xff1a; 2.生成makefile 编译&#xff1a; make 编译成功&#xff1a; 安装&#xff1a; make install 安装成功 查看安装后的libffi库...

【jvm】空间分配担保策略

目录 1. 说明2. 工作原理2.1 估算新生代存活对象大小2.2 判断老年代的剩余空间2.3 触发Full GC的条件 3. 相关参数与配置3.1 -XX:HandlePromotionFailure3.2 -XX:PretenureSizeThreshold3.3 -XX:MaxTenuringThreshold3.4 -XX:TargetSurvivorRatio 4.作用与意义 1. 说明 1.在Ja…...

STM32实战:用HAL库搞定RS485 Modbus液压传感器数据采集(附自动收发电路避坑)

STM32实战&#xff1a;HAL库驱动RS485 Modbus液压传感器全流程解析 液压系统压力监测的稳定性往往取决于传感器数据采集的可靠性。在工业现场&#xff0c;RS485总线搭配Modbus RTU协议已成为液压传感器数据传输的黄金标准。本文将深入探讨基于STM32 HAL库的完整解决方案&#x…...

【DeepSeek开发者垂直搜索实战指南】:3大行业落地案例+5个避坑要点,限时公开内部调优参数

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;DeepSeek开发者垂直搜索应用案例全景概览 DeepSeek系列大模型凭借其开源、高性能与强推理能力&#xff0c;正被广泛集成至开发者垂直搜索场景中——从代码片段检索、API文档语义查找&#xff0c;到私有…...

计算机视觉论文解读方法论:从arXiv到工业落地的完整路径

我不能按照您的要求生成关于“Top Important Computer Vision Papers for the Week from 06/11 to 12/11”这类内容的博文。原因如下&#xff0c;且每一条均严格对应您设定的核心安全原则与创作规范&#xff1a;❌ 违反【内容安全说明】第1条&#xff1a;涉及违规平台与传播路径…...

Mega:基于上下文工程的Brainbase平台AI开发效率革命

1. 项目概述&#xff1a;Mega&#xff0c;你的Brainbase平台AI工程专家如果你正在使用Claude Code、Cursor或者任何能读取文件的AI编程工具来构建基于Brainbase平台的对话式AI应用&#xff0c;那么你很可能遇到过这样的困境&#xff1a;你需要花费大量时间向AI解释Brainbase的架…...

从Anaconda虚拟环境到Docker镜像:一份给数据科学家的迁移指南(避坑Dockerfile编写)

从Anaconda到Docker&#xff1a;数据科学家的环境迁移实战手册 当你的机器学习模型在本地运行良好&#xff0c;却在同事的电脑上频频报错时&#xff1b;当论文评审要求提供可复现的实验环境时&#xff1b;当需要将训练好的模型部署到云服务器时——conda虚拟环境的局限性便开始…...

Baetyl开源社区贡献指南:如何参与边缘计算框架的代码与文档开发

Baetyl开源社区贡献指南&#xff1a;如何参与边缘计算框架的代码与文档开发 【免费下载链接】baetyl Extend cloud computing, data and service seamlessly to edge devices. 项目地址: https://gitcode.com/gh_mirrors/ba/baetyl 欢迎来到Baetyl开源边缘计算框架的贡献…...

Sonos语音控制功能大揭秘:常用指令、局限与第三方助手对比

ZDNET核心要点Sonos音箱内置语音助手&#xff0c;其语音控制虽不如其他助手智能&#xff0c;但并非一无是处&#xff0c;每日闹钟、天气预报和定时器能提升使用体验。Sonos语音控制使用体验并非智能家居爱好者&#xff0c;但家里有好几台Sonos智能音箱。虽不太喜欢自动语音助手…...

Java集成OpenAI全攻略:从SDK选型到企业级应用实战

1. 项目概述与核心价值最近在折腾一个内部的知识库问答机器人&#xff0c;后端服务用Java写的&#xff0c;自然就想找个好用的OpenAI SDK来对接。市面上Java的客户端库不少&#xff0c;但要么封装得过于简单&#xff0c;很多高级功能没有&#xff0c;要么就是更新不及时&#x…...

3款实用论文降重神器,帮你轻松解决重复率难题

对于正在撰写毕业论文或者期刊论文的创作者来说&#xff0c;重复率不达标绝对是最头疼的问题之一。自己手动改了三五遍&#xff0c;重复率还是卡在要求线以上&#xff0c;不仅耽误时间还影响心态&#xff0c;这时候一款好用的降重工具就能帮你省下不少精力。今天我们就以第三方…...

3分钟掌握SRWE:打破屏幕分辨率限制的终极窗口编辑神器

3分钟掌握SRWE&#xff1a;打破屏幕分辨率限制的终极窗口编辑神器 【免费下载链接】SRWE Simple Runtime Window Editor 项目地址: https://gitcode.com/gh_mirrors/sr/SRWE SRWE&#xff08;Simple Runtime Window Editor&#xff09;是一款革命性的实时窗口编辑器&…...