在后台开发中,会经常用到线程池技术,对于线程池核心参数的配置很大程度上依靠经验。然而,由于系统运行过程中存在的不确定性,我们很难一劳永逸地规划一个合理的线程池参数。在对线程池配置参数进行调整时,一般需要对服务进行重启,这样修改的成本就会偏高。一种解决办法就是,将线程池的配置放到平台侧,运行开发同学根据系统运行情况对核心参数进行动态配置。

本文以Nacos作为服务配置中心,以修改线程池核心线程数、最大线程数为例,实现一个简单的动态化线程池。

代码实现

1.依赖

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>2021.1</version>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><version>2021.1</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId>
</dependency>

2.配置yml文件

bootstrap.yml:

server:port: 8010# 应用名称(nacos会将该名称当做服务名称)
spring:application:name: order-servicecloud:nacos:discovery:namespace: publicserver-addr: 192.168.174.129:8848config:server-addr: 192.168.174.129:8848file-extension: yml

application.yml:

spring:profiles:active: dev

为什么要配置两个yml文件?

springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application。

nacos在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后才能保证项目的正常启动。

3.nacos配置

登录到nacos管理页面,新建配置,如下图所示:

注意Data ID的命名格式为,${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension} ,在本文中,Data ID的名字就是order-service-dev.yml

这里我们只配置了两个参数,核心线程数量和最大线程数。

4.线程池配置和nacos配置变更监听

@RefreshScope
@Configuration
public class DynamicThreadPool implements InitializingBean {@Value("${core.size}")private String coreSize;@Value("${max.size}")private String maxSize;private static ThreadPoolExecutor threadPoolExecutor;@Autowiredprivate NacosConfigManager nacosConfigManager;@Autowiredprivate NacosConfigProperties nacosConfigProperties;@Overridepublic void afterPropertiesSet() throws Exception {//按照nacos配置初始化线程池threadPoolExecutor = new ThreadPoolExecutor(Integer.parseInt(coreSize), Integer.parseInt(maxSize), 10L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(10),new ThreadFactoryBuilder().setNameFormat("c_t_%d").build(),new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println("rejected!");}});//nacos配置变更监听nacosConfigManager.getConfigService().addListener("order-service-dev.yml", nacosConfigProperties.getGroup(),new Listener() {@Overridepublic Executor getExecutor() {return null;}@Overridepublic void receiveConfigInfo(String configInfo) {//配置变更,修改线程池配置System.out.println(configInfo);changeThreadPoolConfig(Integer.parseInt(coreSize), Integer.parseInt(maxSize));}});}/*** 打印当前线程池的状态*/public String printThreadPoolStatus() {return String.format("core_size:%s,thread_current_size:%s;" +"thread_max_size:%s;queue_current_size:%s,total_task_count:%s", threadPoolExecutor.getCorePoolSize(),threadPoolExecutor.getActiveCount(), threadPoolExecutor.getMaximumPoolSize(), threadPoolExecutor.getQueue().size(),threadPoolExecutor.getTaskCount());}/*** 给线程池增加任务** @param count*/public void dynamicThreadPoolAddTask(int count) {for (int i = 0; i < count; i++) {int finalI = i;threadPoolExecutor.execute(new Runnable() {@Overridepublic void run() {try {System.out.println(finalI);Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}}});}}/*** 修改线程池核心参数** @param coreSize* @param maxSize*/private void changeThreadPoolConfig(int coreSize, int maxSize) {threadPoolExecutor.setCorePoolSize(coreSize);threadPoolExecutor.setMaximumPoolSize(maxSize);}
}

这个代码就是实现动态线程池和核心了,需要说明的是:

  • @RefreshScope:这个注解用来支持nacos的动态刷新功能;

  • @Value("${max.size}")@Value("${core.size}"):这两个注解用来读取我们上一步在nacos配置的具体信息;同时,nacos配置变更时,能够实时读取到变更后的内容

  • nacosConfigManager.getConfigService().addListener:配置监听,nacos配置变更时实时修改线程池的配置。

5.controller

为了观察线程池动态变更的效果,增加Controller类。

@RestController
@RequestMapping("/threadpool")
public class ThreadPoolController {@Autowiredprivate DynamicThreadPool dynamicThreadPool;/*** 打印当前线程池的状态*/@GetMapping("/print")public String printThreadPoolStatus() {return dynamicThreadPool.printThreadPoolStatus();}/*** 给线程池增加任务** @param count*/@GetMapping("/add")public String dynamicThreadPoolAddTask(int count) {dynamicThreadPool.dynamicThreadPoolAddTask(count);return String.valueOf(count);}
}

6.测试

启动项目,访问http://localhost:8010/threadpool/print打印当前线程池的配置。

可以看到,这个就是我们之前在nacos配置的线程数。

访问http://localhost:8010/threadpool/add?count=20增加20个任务,重新打印线程池配置

可以看到已经有线程在排队了。

为了能够看到效果,我们多访问几次/add接口,增加任务数,在控制台出现拒绝信息时调整nacos配置。

此时,执行/add命令时,所有的线程都会提示rejected。

调整nacos配置,将核心线程数调整为50,最大线程数调整为100.

重新多次访问/add接口增加任务,发现没有拒绝信息了。这时,打印具体的线程状态,发现线程池参数修改成功。

总结

这里,只是简单实现了一个可以调整核心线程数和最大线程数的动态线程池。具体的线程池实现原理可以参考美团的这篇文章:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html,结合监控告警等实现一个完善的动态线程池产品。

优秀的轮子还有好多,比如Hippo4J ,使用起来和dynamic-tp差不多。Hippo4J 有无依赖中间件实现动静线程池,也有默认实现Nacos和Apollo的版本,而dynamic-tp 默认实现依赖Nacos或Apollo。

利用 Nacos 实现了一个动态化线程池,非常实用相关推荐

  1. JAVA 多线程 JAVA 如何开发一个自定义线程池

    1.多线程设计介绍 每一个线程的启动和结束都是比较消耗时间和占用资源的. 如果在系统中用到了很多的线程,大量的启动和结束动作会导致系统的性能变卡,响应变慢. 为了解决这个问题,引入线程池这种设计思想. ...

  2. 20.案例实战:为@Async实现一个自定义线程池

    代码:https://github.com/NIGHTFIGHTING/spring_boot_learning/tree/master/19-20/agan-boot/agan-boot-async ...

  3. 利用C++语言设计可扩展线程池

    摘要:在各种业务解决方案的设计中,服务器处理任务的效率是衡量方案优劣的一个重要标准.使用多线程技术并发处理任务是提高服务器效率的一个主要手段.但是频繁的线程创建.销毁和任务的分配也会降低系统效率.本文 ...

  4. 一个JDK线程池BUG引发的GC机制思考

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:空无 来源:https://urlify.cn/63QrYv ...

  5. 程序员修仙之路-数据结构之设计一个高性能线程池

    原因排查 经过一个多小时的代码排查终于查明了线上程序线程数过多的原因:这是一个接收mq消息的一个服务,程序大体思路是这样的,监听的线程每次收到一条消息,就启动一个线程去执行,每次启动的线程都是新的.说 ...

  6. 一个 Java 线程池bug引发的 GC 机制思考

    本文作者:空无 原文链接:https://segmentfault.com/a/1190000021109130 问题描述 前几天,在帮同事排查一个线上偶发的线程池错误 逻辑很简单,线程池执行了一个带 ...

  7. linux下boost的一个扩展线程池-threadpool-的学习

    转自:http://www.cnblogs.com/xiaouisme/archive/2012/10/04/2711691.html 安装boost: http://www.boost.org/下载 ...

  8. java如何关闭一个线程_如何关闭一个java线程池

    Java 并发工具包中 java.util.concurrent.ExecutorService 接口定义了线程池任务提交.获取线程池状态.线程池停止的方法等. JDK 1.8 中,线程池的停止一般使 ...

  9. 推荐一个强大的开源动态线程池项目

    今天和大家分享的是一个动态线程池,Hippo4J Hippo4J,是一个基于美团动态线程池的设计理念,针对该类线程池,在一般的基础上增强了动态调参.监控.报警等功能的一个版本. Hippo4J可以通过 ...

最新文章

  1. 监听指定端口数据交互(HttpListenerContext )
  2. [zz]启动apache后访问系统,提示没有权限访问目录,报403错误。
  3. 如何用python画一朵玫瑰花-使用Python画一朵美丽的玫瑰花
  4. Spark使用总结与分享
  5. [CVPR2019]:专门为卷积神经网络设计的训练方法:RePr
  6. mysqldumpslow mysql慢日志分析工具
  7. yum 安装mysql的位置_Yum安装MySQL以及相关目录路径和修改目录
  8. 程序员挑战高薪,你必须会的十大面试题《一》
  9. OpenDaylight系列教程(十一)-- Installing OpenDaylight
  10. 支付宝架构师眼中的高并发架构,真是绝了!
  11. 航模飞机设计基础知识
  12. 北理珠计算机学院罗晓莹,北理珠的“程序猿”们勇夺全国大奖!
  13. 微信群总是有人发广告?看我用Python写一个自动化机器人消灭他!
  14. linux部署qq机器人记录
  15. 农夫、羊、菜和狼的故事
  16. linux中如何查看文件上下文,linux通过grep根据关键字查找日志文件上下文
  17. 今天汉化了一个便签软件A note,遵守GNU的自由软件
  18. dlopen failed: couldn‘t map “/data/xxxx.so“ segment 1: Permission denied
  19. 研磨23种大话设计模式------适配器模式
  20. 阿里云ECS服务器开放端口号

热门文章

  1. Ps—导出:sql作业配合ps导出csv文件
  2. win和安卓平板拓展屏幕——spacedesk
  3. 手写 Spring MVC
  4. Centos7 bind dns缓存服务器搭建?dns缓存在哪?如何验证是缓存在生效?
  5. W800|WIFI|CDK|W80X SDK v1.00.10|官方demo|学习(2):t-connect
  6. Swift:我的第三个Demo
  7. 美国虚拟主机常用的两块控制面板——cPanel和Plesk
  8. java swing 小游戏 炸弹人
  9. 计算机二级数据库试题6,2016计算机二级Access真题及答案
  10. linux ip工具