Java避坑案例 - 线程池未复用引发的故障复盘及源码分析

文章目录

  • 问题现象
  • 问题定位
  • 问题代码
  • 根因分析
    • 现象剖析
    • newCachedThreadPool 源码
      • SynchronousQueue
        • 特点
        • 构造方法
        • 主要方法
        • 应用场景
        • Code Demo
        • 运行结果
  • 问题修复

在这里插入图片描述


问题现象

时不时有报警提示线程数过多,超过2000 个,收到报警后查看监控发现,瞬时线程数比较多但过一会儿又会降下来,线程数抖动很厉害,而应用的访问量变化不大。


问题定位

为了定位问题,在线程数比较高的时候进行线程栈抓取,抓取后发现内存中有 1000 多个自定义线程池。

一般而言,线程池肯定是复用的, 1000 多个线程池肯定不正常啊。。。。。。。。


问题代码

在项目代码里,我们没有搜到声明线程池的地方,搜索 execute 关键字后定位到,原来是业务代码调用了一个类库来获得线程池,类似如下的业务代码:调用 ThreadPoolHelpergetThreadPool 方法来获得线程池,然后提交数个任务到线程池处理,并没有看出什么异常

 public String smiulate() throws InterruptedException {ThreadPoolExecutor threadPool = ThreadPoolHelper.getThreadPool();IntStream.rangeClosed(1, 10).forEach(i -> {threadPool.execute(() -> {String payload = IntStream.rangeClosed(1, 1000000).mapToObj(__ -> "a").collect(Collectors.joining("")) + UUID.randomUUID().toString();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}log.debug(payload);});});return "OK";}

继续看 ThreadPoolHelpergetThreadPool 方法居然是每次都使用 Executors.newCachedThreadPool 来创建一个线程池

 public static ThreadPoolExecutor getThreadPool() {// 线程池没有复用return (ThreadPoolExecutor) Executors.newCachedThreadPool();}

根因分析

现象剖析

可以想到 newCachedThreadPool 会在需要时创建必要多的线程,业务代码的一次业务操作会向线程池提交多个慢任务,这样执行一次业务操作就会开启多个线程。如果业务操作并发量较大的话,的确有可能一下子开启几千个线程。

为什么我们能在监控中看到线程数量会下降,而不会撑爆内存呢?回到 newCachedThreadPool 的定义就会发现,它的核心线程数是 0,而 keepAliveTime是 60 秒,也就是在 60 秒之后所有的线程都是可以回收的。好吧,就因这个特性,业务程序并灭有死得没太难看。。。。。。


newCachedThreadPool 源码

看下 newCachedThreadPool的源码

public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}

该线程池特性:

  • 线程池会根据需要创建新线程,但会重用已有的空闲线程。
  • 如果没有可用的空闲线程,则会创建新的线程并添加到池中。
  • 空闲超过60秒的线程会被终止并从池中移除。

参数说明:

  • 0:核心线程数为0,即初始时不会创建任何线程。
  • Integer.MAX_VALUE:最大线程数为整型的最大值,表示可以创建大量线程。
  • 60L:线程空闲时间,单位为秒。
  • TimeUnit.SECONDS:时间单位为秒。
  • new SynchronousQueue<Runnable>():任务队列,使用同步队列,确保每个任务都会立即被线程处理。
开始
创建ThreadPoolExecutor实例
设置核心线程数为0
设置最大线程数为Integer.MAX_VALUE
设置线程空闲时间为60秒
设置时间单位为秒
设置任务队列为SynchronousQueue
返回ExecutorService实例
结束

SynchronousQueue

SynchronousQueue 是 Java 并发包 java.util.concurrent 中的一个特殊的阻塞队列。它的设计目的是为了实现线程之间的直接传递,而不是存储元素。

特点
  • 无缓冲:
    SynchronousQueue 不存储元素,它只是一个直接传递的机制。每个插入操作必须等待另一个线程的对应移除操作,反之亦然。

  • 一对一传递:

    每个插入操作(put)必须等待一个对应的移除操作(take),反之亦然。这意味着生产者线程必须等待消费者线程准备好接收元素,消费者线程也必须等待生产者线程提供元素。

  • 高效:
    由于 SynchronousQueue 不存储元素,因此在某些场景下可以提供更高的性能,特别是在需要快速传递数据的情况下。

  • 公平性选项:
    SynchronousQueue 提供了公平性和非公平性两种模式。公平模式下,线程按照 FIFO 顺序进行匹配;非公平模式下,线程可能会抢占匹配机会。

  • 线程安全:
    SynchronousQueue 是线程安全的,内部使用锁和条件变量来确保线程间的同步。


构造方法

在这里插入图片描述

SynchronousQueue 提供了两个构造方法:

  • SynchronousQueue():默认构造方法,使用非公平模式。
  • SynchronousQueue(boolean fair):指定是否使用公平模式

主要方法
  • void put(E e):将指定的元素插入队列,如果当前没有消费者线程在等待,则阻塞直到有消费者线程。
  • E take():获取并移除队列中的一个元素,如果当前没有生产者线程在等待,则阻塞直到有生产者线程。
  • boolean offer(E e):尝试将指定的元素插入队列,如果当前有消费者线程在等待,则立即返回 true,否则返回 false。
  • boolean offer(E e, long timeout, TimeUnit unit):尝试将指定的元素插入队列,如果当前有消费者线程在等待,则立即返回 true,否则等待指定的时间,如果超时则返回 false。
  • E poll():尝试从队列中获取并移除一个元素,如果当前有生产者线程在等待,则立即返回该元素,否则返回 null。
  • E poll(long timeout, TimeUnit unit):尝试从队列中获取并移除一个元素,如果当前有生产者线程在等待,则立即返回该元素,否则等待指定的时间,如果超时则返回 null。

应用场景

适用于需要快速传递数据且不需要缓冲的场景

  • 工作窃取(Work Stealing)
    在多线程环境中,工作窃取算法通过让空闲线程从其他线程的任务队列中“窃取”任务来提高负载均衡。SynchronousQueue 可以用于实现这种任务传递。

  • 事件驱动系统
    在事件驱动系统中,事件处理器之间需要快速传递事件,SynchronousQueue 可以用于实现这种快速传递。

  • 管道通信
    在管道通信中,生产者和消费者之间需要直接传递数据,SynchronousQueue 可以用于实现这种直接传递。


Code Demo

import java.util.concurrent.SynchronousQueue;
public class SynchronousQueueExample {public static void main(String[] args) {SynchronousQueue<String> queue = new SynchronousQueue<>();// 生产者线程Thread producer = new Thread(() -> {try {System.out.println("生产者线程准备插入数据");queue.put("Hello");System.out.println("生产者线程插入数据完成");} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 消费者线程Thread consumer = new Thread(() -> {try {System.out.println("消费者线程准备获取数据");String data = queue.take();System.out.println("消费者线程获取到数据: " + data);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});producer.start();consumer.start();}
}
开始
创建SynchronousQueue实例
启动生产者线程
启动消费者线程
生产者线程尝试插入数据
生产者线程等待消费者线程
消费者线程尝试获取数据
消费者线程获取数据
生产者线程继续执行
消费者线程继续执行
结束
运行结果
生产者线程准备插入数据
消费者线程准备获取数据
生产者线程插入数据完成
消费者线程获取到数据: Hello

问题修复

使用一个静态字段来存放线程池的引用,返回线程池的代码直接返回这个静态字段即可。

最佳实践,手动创建线程池

	import com.google.common.util.concurrent.ThreadFactoryBuilder;static class ThreadPoolHelper {private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 50,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(1000),new ThreadFactoryBuilder().setNameFormat("demo-threadpool-%d").build());static ThreadPoolExecutor getThreadPool() {return threadPoolExecutor;}}

在这里插入图片描述

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

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

相关文章

AHB Matrix 四星级 验证笔记(1.8-1.9)scoreboard的实现

文章目录 前言一、scoreboard接收数据的方式和比较行为的策略选择1.接受数据的方式1. 首先是数据从哪儿来&#xff1f; 2.比较数据的方式1.方案一2.方案二3. 方案的选择 二、scoreboard的实现1.代码 三、tip1.编辑断点2. return3.有关函数的返回值&#xff1a;函数内部隐式声明…

商业潜规则揭秘:从成交艺术到客户满意度的全方位策略

潜规则一&#xff1a;成交的艺术——七大核心原则 顾客追求的是超值感&#xff0c;而非单纯低价。 与顾客讨论的重点应是价值&#xff0c;而非价格。 客户没有绝对的对错&#xff0c;关键在于服务是否到位。 销售方式比销售产品本身更重要。 没有绝对最好的产品&#xff0c;只有…

在IDEA2024中生成SpringBoot模板

1、创建新项目 根据自己想要创建的工程类型选择&#xff0c;这里创建的时web工程 生成项目&#xff1a; 注意&#xff1a;SpringBoot只会扫描主程序所在的包及其下面的子包

物流行业信息化整体规划方案|117页PPT

文件是关于“物流行业信息化整体规划方案”的详细规划报告&#xff0c;涵盖了物流信息化咨询项目的规划报告&#xff0c;包括业务理解与需求分析、信息化现状分析、信息化蓝图规划以及实施路径与保障措施等多个方面。以下是对文档内容的总结&#xff1a; 1. 引言&#xff1a;信…

opencv优秀文章集合

文章目录 一、 CV领域1.1 图像处理1.2 目标检测与识别1.3 图像分割、目标追踪1.4 姿态估计1.5 3D视觉1.6 图像生成1.7 机器视觉1.8 其它 二、 nlp三、语音四、推荐系统 《OpenCV优秀文章集合》《OpenCV系列课程一&#xff1a;图像处理入门&#xff08;读写、拆分合并、变换、注…

Windows转Mac过渡指南

最近由于工作原因开始使用mac电脑&#xff0c;说实话刚拿到手的时候&#xff0c;window党表示真的用不惯。坚持用一下午之后&#xff0c;发现真的yyds&#xff0c;这篇文章说说mac电脑的基本入门指南。 1. 不会使用mac的触摸板&#xff0c;接上鼠标发现滚轮和windows是反的。 …

字符串逆序(c语言)

错误代码 #include<stdio.h>//字符串逆序 void reverse(char arr[], int n) {int j 0;//采用中间值法//访问数组中第一个元素和最后一个元素//交换他们的值&#xff0c;从而完成了字符串逆序//所以这个需要临时变量for (j 0; j < n / 2; j){char temp arr[j];arr[…

安全成为大模型的核心;大模型安全的途径:大模型对齐

目录 安全成为大模型的核心 大模型安全的途径:大模型对齐 人类反馈强化学习(RLHF) 直接偏好优化(DPO) 安全成为大模型的核心 大模型安全的途径:大模型对齐 大模型对齐技术(Alignment Techniques for Large Language Models)是确保大规模语言模型(例如GPT-4)的输…

K8s企业应用之容器化迁移

#作者&#xff1a;曹付江 K8s企业应用之容器化迁移 Kubernetes&#xff08;K8s&#xff09;中的企业应用容器化迁移是一个复杂但重要的过程&#xff0c;平滑的迁移应用&#xff0c;可以让开发、运维、测试人员循序渐进的学习和掌握Kubernetes&#xff0c;通常包括以下步骤&am…

redis详细教程(3.hash和set类型)

hash Redis中的Hash是一种数据结构&#xff0c;用于存储键值对集合。在Redis中&#xff0c;Hash非常适合表示对象&#xff0c;其中对象的每个字段都对应一个键值对。以下是关于Redis中Hash的详细讲解&#xff1a; 特点&#xff1a; 1. 键值对集合&#xff1a;Hash是一个包含…

linux 安装php扩展:xlswriter

这里以xlswriter扩展为例 进入官方扩展&#xff1a;https://pecl.php.net查询自己php对应版本的扩展包 下载扩展 wget https://pecl.php.net/get/xlswriter-1.5.5.tgz 解压扩展 tar -zxvf xlswriter-1.5.5.tgz 进入扩展目录 cd xlswriter-1.5.5 查找对应php版本的phpiz…

SSID,即Service Set Identifier(服务设置的表示符号)

一、什么是SSID&#xff1f; SSID&#xff0c;即Service Set Identifier&#xff0c;是无线网络中的一个标识符&#xff0c;用于区分不同的无线网络。它可以理解为无线网络的名称&#xff0c;当我们在手机或电脑上搜索可用的无线网络时&#xff0c;就是通过SSID来识别和连接的…

LabVIEW过程控制实验平台

A3000实验平台通过LabVIEW开发&#xff0c;实现了过程控制的虚拟仿真与实时通信&#xff0c;显著提高了教学与实验的互动性和效率。该平台采用模块化设计&#xff0c;支持多种控制策略的实验教学&#xff0c;克服了传统实验设备的不足。项目背景 目前高校过程控制实验设备普遍…

强大的文本编辑器Notepad++8.4.6 最新版

Notepad最新版是一款多功能的代码编辑工具。Notepad官方版支持27种编程语言&#xff0c;涵盖C、C 、Java 、C#,、XML、 HTML,、PHP、python等等&#xff0c;能够帮助程序员提高编辑效率。Notepad软件支持python与sql代码高亮功能&#xff0c;并且免费开源&#xff0c;能够完美地…

Halcon 2D测量Metrology找线/圆/矩形/椭圆

通过2D测量&#xff0c;可以获取物体的范围、方向、角度、位置、尺寸和个数等特征。其中&#xff0c;Halcon的2D Metrology模块提供了亚像素级别的卡尺测量功能&#xff0c;可以测量的几何形状包括直线、圆、椭圆、矩形等。对于2D度量&#xff0c;必须提供要测量的对象的位置&a…

PostgreSQL的学习心得和知识总结(一百五十七)|新的 COPY 选项 LOG_VERBOSITY

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

分析 std::optional 的使用与常见错误

文章目录 引言常见错误及解决方案1. 错误使用 std::optional 变量进行算术运算2. 错误检查 std::optional 是否有值3. 忽视 std::optional 的默认值 结论 引言 std::optional 是 C17 引入的一个模板类&#xff0c;用于表示可能有也可能没有值的情况。它特别适用于函数返回值&a…

大模型中的token是什么;常见大语言模型的 token 情况

目录 大模型中的token是什么 常见大语言模型的 token 情况 大模型中的token是什么 定义 在大模型中,token 是文本处理的基本单位。它可以是一个字、一个词,或者是其他被模型定义的语言单元。简单来说,模型在理解和生成文本时,不是以完整的句子或段落为单位进行一次性处理…

深度了解flink(七) JobManager(1) 组件启动流程分析

前言 JobManager是Flink的核心进程&#xff0c;主要负责Flink集群的启动和初始化&#xff0c;包含多个重要的组件(JboMaster&#xff0c;Dispatcher&#xff0c;WebEndpoint等)&#xff0c;本篇文章会基于源码分析JobManagr的启动流程&#xff0c;对其各个组件进行介绍&#x…

深度学习模型入门教程指南

在当前的人工智能生成内容&#xff08;AIGC&#xff09;领域中&#xff0c;深度学习模型无疑是支撑其技术核心的关键组件。深度学习模型的广泛应用极大地推动了图像生成、自然语言处理和自动化工作流的发展&#xff0c;本文将从多个角度介绍深度学习模型的概念、构建过程、实际…