晕~线程池参数该怎么配置?这可能是为数不多的好答案

前言

CPU 密集型 = CPU 核数 + 1

IO 密集型 = CPU 核数 * 2

相信这个公式可谓是线程池八股文中老生常谈的万能公式了,但现实却很骨感,我之前有个系统就是按照这个公式算出来的参数去配置的。结果效果并不好,甚至让下游系统直呼受不了。这个东西怎么说呢,还是得记住,面试的时候有用 (╯#-_-)╯╧═╧。真实场景中只能得到一个参考值,基于这个参考值,再去进行调整。但是调整这种东西可不怎么友好....

针对这种尴尬的情况,本文基于Java线程池实现原理及其在美团业务中的实践 - 美团技术团队 (meituan.com)这篇文章进行了个总结,希望给大家配置线程池参数有新的启发

美团文章调研的现有解决方案

  • 第一个就是我们上面说的,和实际业务场景有所偏离。
  • 第二个设置为 2*CPU 核心数,有点像是把任务都当做 IO 密集型去处理了。而且一个项目里面一般来说不止一个自定义线程池吧?比如有专门处理数据上送的线程池,有专门处理查询请求的线程池,这样去做一个简单的线程隔离。但是如果都用这样的参数配置的话,显然是不合理的。
  • 第三个不说了,理想状态。流量是不可能这么均衡的,就拿美团来说,下午3,4点的流量,能和 12 点左右午饭时的流量比吗?

基于上面的这些解决方案的痛点,美团给出了动态化配置的解决方案。

动态更新工作原理

  1. 在运行期线程池使用方调用此方法设置corePoolSize之后,线程池会直接覆盖原来的corePoolSize值,并且基于当前值和原始值的比较结果采取不同的处理策略。
  2. 对于当前值小于当前工作线程数的情况,说明有多余的worker线程,此时会向当前idle的worker线程发起中断请求以实现回收,多余的worker在下次idel的时候也会被回收;
  3. 对于当前值大于原始值且当前队列中有待执行任务,则线程池会创建新的worker线程来执行队列任务,setCorePoolSize具体流程

因此动态更新线程参数的核心在于:

setCorePoolSize、setMaxNumPoolSize 以及重新设置队列长度三个方法。

丐版动态更新线程池配置组件

由于美团的文章发表只是理论上的概念并未发布源码。因此参考美团文章给出的思路我来尝试实现微服务的动态更新线程池参数的Stater.

新建一个动态调整线程池参数的Stater,命名为 iread-threadfactory

2: 由于需要调整最大线程数、核心线程数、队列长度三个参数,因此将三个参数做成可配置的,又因为需要辨别每个线程,因此还需要设置线程池的名字。因此建立如下配置类:

WoreadThreadFactoryProperties

java 复制代码
package com.cn.woread.configuration;
​
import java.util.ArrayList;
import java.util.List;
​
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
​
@ConfigurationProperties(prefix = "woread.thread")
public class WoreadThreadFactoryProperties {
​
    List<Properties>list=new ArrayList<Properties>();
​
    public List<Properties> getList() {
        return list;
    }
​
    public void setList(List<Properties> list) {
        this.list = list;
    }
}

Properties

arduino 复制代码
@Data
public class Properties {
    
    private String threadFactoryName;
    
    //最大线程数
    private int maximumPoolSize;
    
    //核心线程数
    private int corePoolSize;
    
    //队列大小
    private int capacity;
}

3:创建线程池创建处理类:WoreadThreadPoolExecture

ini 复制代码
public class WoreadThreadPoolExecture {
​
    private WoreadThreadFactoryProperties properties;
    
    private static Map<String,ThreadPoolExecutor> threadFactorys=new HashMap<String,ThreadPoolExecutor>();
    
    
    public WoreadThreadPoolExecture(WoreadThreadFactoryProperties properties) {
        this.properties=properties;
    }
​
    public ThreadPoolExecutor reBulidThreadFactory(String name) {
​
        // 获取配置列表
        List<Properties> list = properties.getList();
​
        // 创建线程工厂实例
        ThreadPoolExecutor threadFactory = null;
​
        // 如果配置列表不为空且有元素
        if (list != null && list.size() != 0) {
​
            // 在配置列表中查找指定名称的配置对象
            Optional<Properties> mapOpt = list
                    .stream()
                    .filter(l -> name.equals(l.getThreadFactoryName()))
                    .findFirst();
​
            // 根据当前运行时数据,设置默认的最大线程数、核心线程数和队列容量
            int maximumPoolSize = Runtime.getRuntime().availableProcessors() + 1;
            int corePoolSize = maximumPoolSize;
            int capacity = 1000;
​
            // 如果找到了指定名称的配置对象
            if (mapOpt.isPresent()) {
                Properties map = mapOpt.get();
                // 设置最大线程数
                maximumPoolSize = map.getMaximumPoolSize();
                // 设置核心线程数
                corePoolSize = map.getCorePoolSize();
                // 设置队列容量
                capacity = map.getCapacity();
            }
​
            // 如果线程工厂字典中已存在指定名称的线程工厂
            if (threadFactorys.containsKey(name)) {
                threadFactory = threadFactorys.get(name);
                // 更新核心线程数和最大线程数
                threadFactory.setCorePoolSize(corePoolSize);
                threadFactory.setMaximumPoolSize(maximumPoolSize);
                // 获取队列并设置容量
                WoreadLinkedBlockingQueue queue = (WoreadLinkedBlockingQueue) threadFactory.getQueue();
                queue.setCapacity(capacity);
                // 预启动所有核心线程
                threadFactory.prestartAllCoreThreads();
            } else {
                // 创建新的线程工厂实例并添加到字典中
                threadFactory = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 60, TimeUnit.SECONDS, new WoreadLinkedBlockingQueue<Runnable>(capacity));
                threadFactorys.put(name, threadFactory);
            }   
        }
​
        // 返回线程工厂实例
        return threadFactory;
    }
}

该类只有一个方法:ThreadPoolExecutor reBulidThreadFactory(String name)

根据线程池名字创建相应参数的线程池(如果从未创建)

根据线程池名字创建相应参数的线程池(如果已经创建---实现动态调整参数的需求)

typescript 复制代码
    private static Map<String,ThreadPoolExecutor> threadFactorys=new HashMap<String,ThreadPoolExecutor>(); 

利用一个静态的map存储所有创建的线程池对象

ini 复制代码
1          threadFactory.setCorePoolSize(corePoolSize);       
2          threadFactory.setMaximumPoolSize(maximumPoolSize);       
3          WoreadLinkedBlockingQueue queue=(WoreadLinkedBlockingQueue)threadFactory.getQueue();       
4          queue.setCapacity(capacity);       
5          threadFactory.prestartAllCoreThreads();

1-2行代码利用配置文件配置的线程数量来重新设置线程参数,可是却未找到重新设置队列长度的方法,通过翻看源码发现,

队列长度capacity被设置成了final对象,不可更改,因此我的做法是重写队列,将大小设置为可改变的,提供改变方法

创建 线程队列类:WoreadLinkedBlockingQueue

4:创建staer类WoreadThreadFactoryConfiguration

less 复制代码
@Configuration
@EnableConfigurationProperties(WoreadThreadFactoryProperties.class)
public class WoreadThreadFactoryConfiguration {
    
    @Autowired
    private WoreadThreadFactoryProperties properties;
    
    @Bean
    @ConditionalOnMissingBean(WoreadThreadPoolExecture.class)
    public WoreadThreadPoolExecture woreadThreadPoolExecture() {
        return new WoreadThreadPoolExecture(properties);
    }
}

至此动态调整参数的线程池stater构建完毕。

本文参考:

线程池参数到底要怎么配?这可能是最好的答案 - 知乎 (zhihu.com)

Java线程池实现原理及其在美团业务中的实践 - 美团技术团队 (meituan.com)

如何正确设置Java线程池参数?「建议收藏」-腾讯云开发者社区-腾讯云 (tencent.com)

相关推荐
昵称为空C7 分钟前
SpringBoot数据存储时区选择,符合国际化和特定时区方案
spring boot·后端
珹洺10 分钟前
C++算法竞赛篇:DevC++ 如何进行debug调试
java·c++·算法
SHUIPING_YANG18 分钟前
根据用户id自动切换表查询
java·服务器·数据库
爱吃烤鸡翅的酸菜鱼30 分钟前
IDEA高效开发:Database Navigator插件安装与核心使用指南
java·开发语言·数据库·编辑器·intellij-idea·database
惊涛骇浪、36 分钟前
SpringMVC + Tomcat10
java·tomcat·springmvc
前端小巷子44 分钟前
Web 实时通信:从短轮询到 WebSocket
前端·javascript·面试
墨染点香1 小时前
LeetCode Hot100【6. Z 字形变换】
java·算法·leetcode
ldj20201 小时前
SpringBoot为什么使用new RuntimeException() 来获取调用栈?
java·spring boot·后端
超龄超能程序猿1 小时前
Spring 应用中 Swagger 2.0 迁移 OpenAPI 3.0 详解:配置、注解与实践
java·spring boot·后端·spring·spring cloud
江南一点雨1 小时前
Tokenizer 和 BPE
后端