一次线程池使用错误导致的问题

记录一次服务线程数量异常问题的排查过程

背景

通过监控发现一个服务的线程数异常多
在这里插入图片描述

同期CPU 内存 网络连接都没有什么异常。

排查

第一个反应就是查看线程栈

"pool-2493-thread-3" #3718833 prio=5 os_prio=0 tid=0x00007f1610041000 nid=0x38bff6 waiting on condition [0x00007f18ba29e000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000007a044cce0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

"pool-2493-thread-2" #3718832 prio=5 os_prio=0 tid=0x00007f161003f800 nid=0x38bff5 waiting on condition [0x00007f18bd502000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000007a044cce0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

"pool-2493-thread-1" #3718823 prio=5 os_prio=0 tid=0x00007f161003e000 nid=0x38bff3 waiting on condition [0x00007f18c2725000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000007a044cce0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

本来想从线程栈中找到线程是从哪里创建的,但从线程栈现在只能看到线程是由ThreadPoolExecutor创建的,只能看到线程运行态的东西。

那么heap中会有吗?

名词
Object/Stack Framejava.lang.Thread @ 0x7a044af30
Namepool-2493-thread-3
Shallow Heap120
Retained Heap1,728
Max. Locals’ Retained Heap
Context Class Loaderorg.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader @ 0x71d665028
Is Daemonfalse
State[alive, parked, waiting, waiting indefinitely]
State value0x291

这里只是比线程栈多了一个Context Class Loader别的没有什么有用的信息。

回看下线程栈信息,发现一个规律[pool-2493-thread-1] pool后面的数字的递增的,但thread后面数字基本都是1 2 3

从这篇文章Naming Executor Service Threads and Thread Pool in Java 可以推断出,应该在代码中有位置初始化了线程池,并且核心线程数是3,在代码中搜索了一下,还真找到了这么一段代码

// import cn.hutool.core.thread.ThreadUtil;

    @GetMapping(value = "/jiankunkingTransfer")
    public Map<String, Object> jiankunkingTransfer(String date) {
        Executor executor = ThreadUtil.newExecutor(3, 6, 30000);
        // todo 执行一些逻辑处理
        for (Object c : json.getJSONArray("data")) {
            executor.execute(() -> {
               // todo 执行一些逻辑处理
            });
        }
		// 注意这里没有用CountDownLatch来await()
		// 也没有shutdown()
    }

一开始以为局部创建的线程会被GC 回收掉,然后通过Arthas vmtool强制GC了一把,发现线程并没有减少。

为啥没有回收呢?

=>
Does an ExecutorService get garbage collected when out of scope?

=> 这里提到了线程池的shutdown(),但此时的我还没有将重点放在这里。
=> 导致我认为没有被回收的线程是全局变量,一顿排查操作并没有发现全局且很大的线程池
=> 再次搜索找到查找答案:
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.html

在JDK的官方文档中找到这么一段话

Finalization
 A pool that is no longer referenced in a program AND has no remaining threads will be shutdown automatically.
 If you would like to ensure that unreferenced pools are reclaimed even if users forget to call shutdown(), 
 then you must arrange that unused threads eventually die, by setting appropriate keep-alive times, using a 
 lower bound of zero core threads and/or setting allowCoreThreadTimeOut(boolean).

=>
程序中不再引用且没有剩余线程的地将自动shutdown。如果您想确保即使用户忘记调用shutdown也能回收未引用的地,那么您必须通过设置适当的保持活动时间、使用零核心线程的下限和/或设置allowCoreThreadTimeOut(boolean)来安排未使用的线程最终死亡allowCoreThreadTimeOut(boolean)

结论

通过官方文档可以看出解决这个问题有下面几种方式

  1. 线程池 设置成 成员变量 (推荐)
  2. 使用threadPoolExecutor.shutdown(); 方法关闭线程池(会等待任务执行结束)
  3. 设置核心线程超时关闭 allowCoreThreadTimeOut(true);
  4. 核心线程数设置为0, 但在使用上会带来别的麻烦(略)

问题的处理方式已经清楚了,下面再来看下shutdown()跟线程池默认的命名规则

拓展

shutdown()实现

shutdown就是将线程池状态设置为SHUTDOWN,然后中断所有空闲(空闲即阻塞在队列上)的线程,最终设置线程池状态为Terminated。

    /**
     * Initiates an orderly shutdown in which previously submitted
     * tasks are executed, but no new tasks will be accepted.
     * Invocation has no additional effect if already shut down.
     *
     * <p>This method does not wait for previously submitted tasks to
     * complete execution.  Use {@link #awaitTermination awaitTermination}
     * to do that.
     *
     * @throws SecurityException {@inheritDoc}
     */
    public void shutdown() {
        
        final ReentrantLock mainLock = this.mainLock;
        // 加锁(全局锁)
        mainLock.lock();
        try {
            // 权限校验,安全策略相关判断
            checkShutdownAccess();
            // 设置线程池状态为SHUTDOWN。
            advanceRunState(SHUTDOWN);
            // 中断所有的空闲的工作线程
            interruptIdleWorkers();
            // 空方法,留给子类实现
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            // 解锁
            mainLock.unlock();
        }
        tryTerminate();
    }

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.html#shutdown–

线程池默认的命名规则

java.util.concurrent.DefaultThreadFactory

static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

从代码中可以看出默认线程的名字是按照=> pool-线程池编号(从1开始自增)-thread-线程池中线程数量(从1开始自增)

如何自定义线程名字可以参考下面这个
https://stackoverflow.com/questions/6113746/naming-threads-and-thread-pools-of-executorservice

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

我为何要用wordpress搭建一个自己的独立博客

我在csdn有一个博客&#xff0c;这个博客是之前学习编程时建立的。 博客有哪些好处呢&#xff1f; 1&#xff0c;可以写自己的遇到的问题和如何解决的步骤 2&#xff0c;心得体会&#xff0c;经验&#xff0c;和踩坑 3&#xff0c;可以转载别人的好的技术知识 4&#xff0c;宝贵…

java毕业设计之基于Bootstrap的常州地方旅游管理系统的设计与实现(springboot)

项目简介 基于Bootstrap的常州地方旅游管理系统的设计与实现有下功能&#xff1a; 基于Bootstrap的常州地方旅游管理系统的设计与实现的主要使用者分为用户功能模块和管理员功能模块两大部分&#xff0c;用户可查看景点信息、景点资讯等&#xff0c;注册登录后可进行景点订票…

面试经典 150 题:189、383

189. 轮转数组 【参考代码】 class Solution { public:void rotate(vector<int>& nums, int k) {int size nums.size();if(1 size){return;}vector<int> temp(size);//k k % size;for(int i0; i<size; i){temp[(i k) % size] nums[i];}nums temp; }…

mysql--多表查询

一、联合查询 作用&#xff1a;合并结果集就是把两个select语句的查询结果合并到一起&#xff01; 合并结果集有两种方式&#xff1a; UNION&#xff1a;合并并去除重复记录&#xff0c;例如&#xff1a;SELECT * FROM t1 UNION SELECT * FROM t2&#xff1b; UNION ALL&a…

什么是严肃游戏,严肃游戏本地化的特点是什么?

“严肃游戏”是一种交互式数字体验&#xff0c;不仅用于娱乐&#xff0c;还用于教育、培训或解决问题。与主要关注乐趣和参与度的传统游戏不同&#xff0c;严肃游戏的目标不仅仅是娱乐&#xff0c;比如教授特定技能、模拟现实生活场景或提高对重要问题的认识。它们用于医疗保健…

ADI常规SHARC音频处理器性能对比

1、 ADSP-2156x:是基于SHARC+ DSP架构的单核32位/40位/64位浮点处理器,不仅具有灵活的音频连接性和性能可扩展性,还提供多个引脚兼容版本(400MHz至1GHz)和多种片内存储器选项,数据手册链接:https://www.analog.com/media/en/technical-documentation/data-sheets/adsp-2…

springboot 整合 抖音 移动应用 授权

后端开发&#xff0c;因为没有JavaSDK&#xff0c;maven依赖&#xff0c;用到的是API接口去调用 抖音API开发文档 开发前先申请好移动应用&#xff0c;抖音控制台-移动应用 之后还需要开通所有能开通的能力 拿到应用的 clientKey 和 clientSecret&#xff0c;就可以进入开发了 …

Python 三维图表绘制指南

Python 三维图表绘制指南 在数据可视化中&#xff0c;三维图表可以更直观地展示数据之间的关系&#xff0c;尤其是当数据具有多个维度时。Python 提供了多个库来绘制三维图表&#xff0c;其中最常用的就是 Matplotlib。本文将介绍如何使用 Matplotlib 绘制三维图表&#xff0c…

Node.js:Express 服务 路由

Node.js&#xff1a;Express 服务 & 路由 创建服务处理请求req对象 静态资源托管托管多个资源挂载路径前缀 路由模块化 Express是Node.js上的一个第三方框架&#xff0c;可以快速开发一个web框架。本质是一个包&#xff0c;可以通过npm直接下载。 创建服务 Express创建一…

计算机网络-以太网小结

前导码与帧开始分界符有什么区别? 前导码--解决帧同步/时钟同步问题 帧开始分界符-解决帧对界问题 集线器 集线器通过双绞线连接终端, 学校机房的里面就有集线器 这种方式仍然属于共享式以太网, 传播方式依然是广播 网桥: 工作特点: 1.如果转发表中存在数据接收方的端口信息…

学生成绩查询系统设计与实现

学生成绩查询系统设计与实现 1. 系统概述 学生成绩查询系统是一个基于PHP和SQL的Web应用程序&#xff0c;旨在为学校提供一个高效的学生成绩管理和查询平台。该系统可以帮助教师录入成绩、学生查询成绩、管理员管理用户和成绩数据&#xff0c;提高教育管理的效率和透明度。 2…

Rust 力扣 - 2653. 滑动子数组的美丽值

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们遍历长度为k的的窗口 因为数据范围比较小&#xff0c;所以我们可以通过计数排序找到窗口中第k小的数 如果小于0&#xff0c;则该窗口的美丽值为第k小的数如果大于等于0&#xff0c;则该窗口的美丽值为0 题…

VisualStudio远程编译调试linux_c++程序(二)

前章讲述了gdb相关&#xff0c;这章主要讲述用VisualStudio调试编译linux_c程序 1&#xff1a;环境 win10 VisualStudio 2022 Community ubuntu22.04 2:安装 1>vs安装时&#xff0c;勾选 使用c进行linux 和嵌入式开发 (这里以vs2022为例) OR VS安装好了&#xff0c; 选择工…

音视频听译:助力多维度沟通与发展的大门

在全球经济一体化的大背景下&#xff0c;企业之间的跨国合作愈发频繁。在商务会议、谈判和产品演示等活动中&#xff0c;语言的多样性成为了一大挑战。而音视频听译服务能够将不同语言的音频准确转换为目标语言文字&#xff0c;确保信息的精准传达&#xff0c;避免因语言障碍引…

基于MATLAB人脸检测的汽车疲劳驾驶检测

课题介绍 疲劳驾驶导致汽车交通事故逐年增加&#xff0c;为了提升驾车的安全性&#xff0c;需对驾驶员疲劳状态实时监测并及时提醒. 为了提高疲劳驾驶判断效率及准确率&#xff0c;本文运用Viola-Jones 框架特征矩阵进行人脸预判断&#xff1b;预判断过程中为了减少Haar 值计算…

论文阅读(三十二):EGNet: Edge Guidance Network for Salient Object Detection

文章目录 1.Introduction2.Related Works3.Salient Edge Guidance Network3.1Complementary information modeling3.1.1Progressive salient object features extraction3.1.2Non-local salient edge features extraction 3.2One-to-one guidance module 4.Experiments4.1Imple…

MySQL超大分页怎么优化处理?limit 1000000,10 和 limit 10区别?覆盖索引、面试题

1. limit 100000,10 和 limit 10区别 LIMIT 100000, 10&#xff1a; 这个语句的意思是&#xff0c;从查询结果中跳过前100000条记录&#xff0c;然后返回接下来的10条记录。这通常用于分页查询中&#xff0c;当你需要跳过大量的记录以获取后续的记录时。例如&#xff0c;如果你…

源码侦探:理解 numpy 中的 tile 方法

文章目录 pre &#xff1a;先来一张源码的切片1. 参数和基本定义&#xff1a;2. 将 reps 转换为元组&#xff1a;3. 提升数组维度&#xff1a;4. 特殊情况检查&#xff1a;5. 处理数组维度的不同情况&#xff1a;6. 计算输出数组的形状&#xff1a;7. 通过重复构造数组&#xf…

单链表OJ题(3):合并两个有序链表、链表分割、链表的回文结构

目录 一、合并两个有序链表 二、链表分割 三、链表的回文结构 u解题的总体思路&#xff1a; 合并两个有序链表&#xff1a;首先创建新链表的头节点&#xff08;哨兵位&#xff1a;本质上是占位子&#xff09;&#xff0c;为了减少一些判断情况&#xff0c;简化操作。然后我们…

Qt6 CMake 中引入 Qt Linguist 翻译功能

qt cmake 使用自带翻译工具配置步骤 创建Qt CMake 程序配置项目 CMake 及 代码使用流程最终CMake 如下最终工程链接为&#xff1a;参考 创建Qt CMake 程序 配置项目 CMake 及 代码 在CMake 中添加如下代码, 导入相关的翻译库 find_package(QT NAMES Qt6 Qt5 REQUIRED COMPON…