ThreadPoolExecutor keepAliveTime 含义

现象

在线上环境排查问题时,某个线程池在某个时间点新建线程达到设定的最大线程数 maximumPoolSize,后续流量降低后当前线程数仍未回落,仍然为最大线程数,阻塞队列中有任务,但是活跃线程数显著减少。

之前的认知

固有的认知中,线程池运行原理:java.util.concurrent.ThreadPoolExecutor#execute

  1. 线程池内部维护 corePoolSize 个线程
  2. 任务提交后,若核心线程都已被占用,则添加到阻塞队列
  3. 阻塞队列已满,则新建线程直到线程数到达 maximumPoolSize
  4. 若阻塞队列已满,并且线程数到达 maximumPoolSize,则执行拒绝策略
  5. 超过 corePoolSize 部分的空闲线程,到达 keepAliveTime 后,进行销毁。

冲突

认知第五点中:超过 corePoolSize 部分的空闲线程,到达 keepAliveTime 后,进行销毁。明显与现象不符。现象肯定没问题的,就是认知有问题了:超过 corePoolSize 部分的空闲线程,到达 keepAliveTime 后,至少不会马上销毁。

现实与认知的问题

  1. 超过 corePoolSize 部分的空闲线程,到达 keepAliveTime 后,会不会销毁?
  2. 销毁的时机是?
  3. 为什么线程池中大多为休眠线程?线程池的线程数仍为最大线程数?

重塑认知

答案都在源码内

ThreadPoolExecutor 执行任务流程

线程池使用 demo

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 10, 100, TimeUnit.MINUTES, new ArrayBlockingQueue<>(1000));threadPoolExecutor.execute(() -> System.out.println("print in thread"));

执行流程

java.util.concurrent.ThreadPoolExecutor#execute

流程就是之前认知中 1 - 4 点,在第三点中蕴含一个重要变量:java.util.concurrent.ThreadPoolExecutor#workers,这个就是ThreadPoolExecutor 管理线程的对象

workers 移除流程

源码上看,只有以下两个方法

java.util.concurrent.ThreadPoolExecutor#addWorkerFailed
java.util.concurrent.ThreadPoolExecutor#processWorkerExit

望文生义,addWorkerFailed 作用为添加 worker 后的失败补偿动作,可以忽略这个方法。

所以正常的销毁动作,肯定是在 processWorkerExit 中。

processWorkerExit 执行流程

使用场景

仅在java.util.concurrent.ThreadPoolExecutor#runWorker 中 finally 执行

而 runWorker 则是任务执行的底层方法,那么这意味着:任务执行完,满足某几个前提条件就会销毁线程。那么前提条件是什么呢?

runWorker 执行流程
  1. while 循环调用 java.util.concurrent.ThreadPoolExecutor#getTask 获取任务
    1. 获取到任务后,走真实执行任务流程,beforeExecute/run/afterExecute
    2. 获取不到任务,则到 processWorkerExit 执行
getTask 执行流程
  1. 使用当前 worker 数与核心线程数关系判定变量 timed
  2. 根据 timed 判定 timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take()

keepAliveTime 第一次出现,并且是用于在当前 worker 数大于核心线程数情况下从阻塞队列中获取元素。

那么,控制 processWorkerExit 执行的前提条件:当前 worker 数大于核心线程数,并且从阻塞队列经过 keepAliveTime 拿不到任务。

但这个前提条件明显跟现象不符,那肯定是 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) 中被阻塞了,导致实际获取任务时间 > keepAliveTime。

workQueue.poll 执行流程(以 ArrayBlockingQueue 为例)
  1. 获取 ArrayBlockingQueue 全局锁
  2. 当队列元素个数 = 0, 则 await keepAliveTime 时间
  3. 队列元素个数 != 0,出队元素
  4. 释放 ArrayBlockingQueue 全局锁
public E poll(long timeout, TimeUnit unit) throws InterruptedException {long nanos = unit.toNanos(timeout);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0) {if (nanos <= 0)return null;nanos = notEmpty.awaitNanos(nanos);}return dequeue();} finally {lock.unlock();}}

真相

从workQueue.poll 执行流程中,能明显看到线程 await 的前提是获取到队列的全局锁,并且队列元素 = 0。

整理一遍就是:

当线程获取到队列全局锁,并且当前队列为空,await keepAliveTime 后,若当前队列为空,则执行销毁方法。

@startuml"Thread" -> "BlockingQueue": pool task
"Thread" -> "BlockingQueue": get global ReentrantLock
alt get global ReentrantLock successalt BlockingQueue size = 0"Thread" -> "Condition": await keepAliveTime"BlockingQueue" -> "Thread": non task,execute processWorkerExit methodelse"BlockingQueue" -> "Thread": first task in queue"Thread" -> "Thread": keep execute taskend
else "Thread" -> "BlockingQueue": keep acquire ReentrantLock
end
@enduml

那么上述提到的两个问题

  1. 超过 corePoolSize 部分的空闲线程,到达 keepAliveTime 后,会不会销毁?
  2. 销毁的时机是?
  3. 为什么线程池中大多为休眠线程?线程池的线程数仍为最大线程数?

就有了答案

  1. 超过 corePoolSize 部分的空闲线程,到达 keepAliveTime 后,有可能销毁,前提是拿到队列的全局锁。

  2. 销毁的时机是当前线程获取到队列全局锁,并且队列元素 = 0,并且 await 后队列元素仍然为 0

  3. 因为线上提交任务刚好够核心线程消费,并且残留少数任务在阻塞队列中。在并发情况下,大部分线程都 await,线程池只能新增 worker 处理了。

自言自语

怎么解决当前线程数 = 最大线程数,并且活跃线程较少的情况?

  1. 调高 corePoolSize ,使线程池不新增 corePoolSize 之外的线程。
  2. 调低 keepAliveTime & TimeUnit 的值,使休眠线程快速被销毁。

在商业开发的角度上,比较难精准实现。

  1. 业务发展速度很快, corePoolSize 在将来的一段时间内就不适合了。
  2. 加快休眠线程的销毁,意味着存在频繁新建线程的问题,会影响系统稳定性。

为什么 await keepAliveTime后不直接销毁?还尝试出队元素?

这就回到 java 线程与操作系统线程的映射关系。

线程模型有三种:一对一,多对一,一对多。java 在大多数平台上都是一对一。

  1. 如果直接销毁,核心线程处理不过来情况下,线程池会频繁销毁/新建线程,消耗系统的资源。
  2. 尝试出队元素,double check 线程池的负载,负载高则继续处理,负载较低则销毁线程,达到节省资源的目的。

keepAliveTime 的理解

源码中的注释

when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.当线程数大于 Core 数时,这是多余的空闲线程在终止之前等待新任务的最长时间。

之前以为是线程数大于 Core 数时,空闲线程的存活时间。过了 keepAliveTime 就执行销毁。

现在认识到:线程数大于 Core 数时,空闲线程的存活时间 >= keepAliveTime (没获取到队列锁的情况下),并且销毁前 double check 是否有任务,没有才执行销毁。

本文首发于cartoon的博客

转载请注明出处:https://cartoonyu.github.io

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

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

相关文章

如何恢复已删除的 Telegram 消息 [iOSamp;Android]

Telegram 是一款功能强大的消息应用程序&#xff0c;因其易用性、隐私保护和众多炫酷功能而深受用户喜爱。然而&#xff0c;有时我们会不小心删除重要的消息。在这种情况下你应该做什么&#xff1f; 本文将为您提供简单有效的解决方案来恢复 Telegram 上已删除的消息&#xff…

Outlook2024版如何回到经典Outlook

Outlook2024版如何回到经典Outlook 如果新加入一家公司&#xff0c;拿到的电脑&#xff0c;大概率是最新版的Windows, 一切都是新的。 如果不coding, 使用国产的foxmail大概就可以解决一切问题了。可惜老程序员很多Coding都是基于传统Outlook的&#xff0c;科技公司所有人都是I…

动态库dll与静态库lib编程4:MFC规则DLL讲解

文章目录 前言一、说明二、具体实现2.1新建项目2.2 模块切换的演示 总结 前言 动态库dll与静态库lib编程4&#xff1a;MFC规则DLL讲解。 一、说明 1.前面介绍的均为Win32DLL&#xff0c;即不使用MFC的DLL。 2.MFC规则DLL的特点&#xff1a;DLL内部可以使用MFC类库、可以被其他…

若依中Feign调用的具体使用(若依微服务版自身已集成openfeign依赖,并在此基础上定义了自己的注解)

若依中Feign调用具体使用 注意&#xff1a;以下所有步骤实现的前提是需要在启动类上加入注解 EnableRyFeignClients 主要是为开启feign接口扫描 1.创建服务提供者(provider) 导入依赖(我在分析依赖时发现若依本身已经引入openfeign依赖,并在此基础上自定义了自己的EnableRyF…

CSS3——3. 书写格式二

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title></head><body><!--css书写&#xff1a;--><!--1. 属性名:属性值--><!--2.属性值是对属性的相关描述--><!--3.属性名必须是…

zookeeper 数据类型

文章目录 引言I Znodezonde stat (状态信息)znode类型临时\永久序列化特性引言 在结构上与标准文件系统非常类似,拥有一个层次的命名空间,都是采用树形层次结构 Zookeeper树中的每个节点被称为:Znode,没有文件和目录之分。Znode兼具文件和目录两种特点Znode存储数据大小有…

Hadoop集群之间实现免密登录

实现虚拟机之间能够互相登录&#xff0c;比如可以在hadoop1上面登录hadoop2。 第一步&#xff1a;执行”ssh-keygen -t rsa”命令&#xff0c;生成该虚拟机的密钥 第二步&#xff1a;密钥文件存储在/root/.ssh目录&#xff0c;执行cd /root/.ssh命令进入存储密钥文件的目录&am…

【linux基础I/O(1)】文件描述符的本质重定向的本质

目录 前言1. 理解C语言的文件接口2. 操作文件的系统调用接口2.1 open函数详解2.2 close函数详解2.3 write函数详解2.4 read函数详解 3. 文件描述符fd详解4. 文件描述符的内核本质5. 怎样理解Linux下一切皆文件?6. 理解输出输入重定向7. 重定向的系统调用8. 总结 前言 “在Lin…

C++:范围for

范围for&#xff08;range-based for&#xff09;是C的一种循环结构&#xff0c; 是在 C11 这个标准中引入的&#xff0c;这种类型的for循环使得遍历数组、容器中的元素更加简便和直观。 一、范围for语法 for ( 类型 变量名 : 数组名 ) 语句 //多条语句需要加⼤括号 示例&#…

C语言 递归编程练习

1.将参数字符串中的字符反向排列&#xff0c;不是逆序打印。 要求&#xff1a;不能使用C函数库中的字符串操作函数。 比如&#xff1a; char arr[] "abcdef"; 逆序之后数组的内容变成&#xff1a;fedcba 1.非函数实现&#xff08;循环&#xff09; 2.用递归方法…

Spring Boot - 日志功能深度解析与实践指南

文章目录 概述1. Spring Boot 日志功能概述2. 默认日志框架&#xff1a;LogbackLogback 的核心组件Logback 的配置文件 3. 日志级别及其配置配置日志级别3.1 配置文件3.2 环境变量3.3 命令行参数 4. 日志格式自定义自定义日志格式 5. 日志文件输出6. 日志归档与清理7. 自定义日…

USB子系统学习(一)USB电气信号

文章目录 1、声明2、USB协议概述3、USB电气信号3.1、USB基础概念3.1.1、低速/全速信号电平3.1.2、高速信号电平 3.2、学习目标3.3、设备断开与连接3.3.1、连接3.3.2、断开 3.4、复位3.5、设备速率识别3.5.1、低速/全速3.5.2、高速 3.6、数据信号3.6.1、低速/全速的SOP和EOP3.6.…

Android GameActivity(NativeActivity)读写文件

最近研究native android相关内容&#xff0c;其中最棘手的就是文件读写问题&#xff0c;最主要的是相关的文档很少。这里写下我所知道的方法。 由于本人使用的是Android14[arm64-v8a]版本的设备,能访问的路径相当有限&#xff0c;如果想要访问更多的路径&#xff0c;就不得不申…

安卓入门十一 常用网络协议四

MQTT&#xff08;Message Queuing Telemetry Transport&#xff09; MQTT是一种轻量级的、发布/订阅模式的消息传输协议。它被设计用于在低带宽或不稳定网络环境下&#xff0c;实现物联网设备之间的可靠通信。 4.1 MQTT详细介绍 发布/订阅模式&#xff1a;MQTT 使用发布/订…

气膜球幕:引领元宇宙时代的科技与艺术光影盛宴—轻空间

在科技与艺术交织的时代&#xff0c;未来的观影体验将不再受限于传统屏幕的束缚。随着气膜球幕的崭新亮相&#xff0c;突破性的光影效果和沉浸式体验让我们走进了一个全新的视听世界。这不仅仅是一个简单的球形影院&#xff0c;它是连接现实与虚拟、科技与艺术、光与影的桥梁&a…

Hyperbolic dynamics

http://www.scholarpedia.org/article/Hyperbolic_dynamics#:~:textAmong%20smooth%20dynamical%20systems%2C%20hyperbolic%20dynamics%20is%20characterized,semilocal%20or%20even%20global%20information%20about%20the%20dynamics. 什么是双曲动力系统&#xff1f; A hy…

kernel32.dll动态链接库报错要怎解决?详细解析kernel32.dll文件缺失解决方案

Kernel32.dll动态链接库报错详解与解决方案 在电脑的日常使用中&#xff0c;我们时常会遇到各种系统报错&#xff0c;其中kernel32.dll文件的报错尤为让人头疼。作为一名在软件开发领域摸爬滚打多年的从业者&#xff0c;我将为大家深入解析kernel32.dll文件的重要性&#xff0…

win10 npm login 登陆失败

npm login 命令总是登陆失败 提示我们设置带proxy。我们本地找到这个 找到代理地址 进行关键信息的设置 npm config set proxy http://xxxx:xxxx npm config set https-proxy http://xxx:xxx 设置完之后在执行npm login即可

[Qt] 输入控件 | Line | Text | Combo | Spin | Date | Dial | Slider

目录 输入类控件 1、Line Edit 录入个人信息 使用正则表达式验证输入框的数据 验证两次输入的密码一致 切换显示密码 2、Text Edit 获取多行输入框的内容 验证输入框的各种信号 3、Combo Box 使用下拉框模拟麦当劳点餐 从文件中加载下拉框的选项 4、Spin Box 调整…

SpringCloud源码-Ribbon

一、Spring定制化RestTemplate&#xff0c;预留出RestTemplate定制化扩展点 org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration 二、Ribbon定义RestTemplate Ribbon扩展点功能 org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguratio…