前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手写一个轻量级动态线程池,很香!!

手写一个轻量级动态线程池,很香!!

作者头像
Guide哥
发布2023-02-06 10:23:51
5980
发布2023-02-06 10:23:51
举报
文章被收录于专栏:JavaGuideJavaGuide

👉 欢迎准备 Java 面试以及学习 Java 的同学加入我的 知识星球 ,干货很多!收费虽然是白菜价,但星球里的内容或许比你参加上万的培训班质量还要高。 👉 《Java 面试指北》 来啦!这是一份教你如何更高效地准备面试的小册,涵盖常见八股文(系统设计、常见框架、分布式、高并发 ......)、优质面经等内容。

Java面试指南网站:javaguide.cn

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

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

说明:实际项目中,我们可直接使用现成的轮子比如 Hippo4J、Dynamic Tp,没必要自己手动实现。这篇文章的目的主要是为了让大家搞懂可以动态修改线程池参数配置的原理。 Hippo4J 我曾经在「优质 Java 开源项目推荐第 12 期」推荐过,Dynamic Tp 我曾经在「优质 Java 开源项目推荐第 13 期」推荐过。

代码实现

1.依赖

代码语言:javascript
复制
<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

代码语言:javascript
复制
server:
  port: 8010
  # 应用名称(nacos会将该名称当做服务名称)
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        namespace: public
        server-addr: 192.168.174.129:8848
      config:
        server-addr: 192.168.174.129:8848
        file-extension: yml

application.yml

代码语言:javascript
复制
spring:
  profiles:
    active: dev

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

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

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

3.nacos 配置

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

注意 Data ID 的命名格式为,{spring.application.name}-{spring.profile.active}.

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

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

代码语言:javascript
复制
@RefreshScope
@Configuration
public class DynamicThreadPool implements InitializingBean {
    @Value("${core.size}")
    private String coreSize;

    @Value("${max.size}")
    private String maxSize;

    private static ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private NacosConfigManager nacosConfigManager;

    @Autowired
    private NacosConfigProperties nacosConfigProperties;

    @Override
    public 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() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println("rejected!");
                    }
                });

        //nacos配置变更监听
        nacosConfigManager.getConfigService().addListener("order-service-dev.yml", nacosConfigProperties.getGroup(),
                new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }

                    @Override
                    public 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() {
                @Override
                public 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("
  • nacosConfigManager.getConfigService().addListener:配置监听,nacos 配置变更时实时修改线程池的配置。

5.controller

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

代码语言:javascript
复制
@RestController
@RequestMapping("/threadpool")
public class ThreadPoolController {

    @Autowired
    private 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。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-01-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JavaGuide 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 代码实现
    • 1.依赖
      • 2.配置 yml 文件
        • 3.nacos 配置
          • 4.线程池配置和 nacos 配置变更监听
            • 5.controller
              • 6.测试
              • 总结
              相关产品与服务
              微服务引擎 TSE
              微服务引擎(Tencent Cloud Service Engine)提供开箱即用的云上全场景微服务解决方案。支持开源增强的云原生注册配置中心(Zookeeper、Nacos 和 Apollo),北极星网格(腾讯自研并开源的 PolarisMesh)、云原生 API 网关(Kong)以及微服务应用托管的弹性微服务平台。微服务引擎完全兼容开源版本的使用方式,在功能、可用性和可运维性等多个方面进行增强。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档