Building a Dynamic Thread Pool with SpringBoot and Nacos

This article demonstrates how to create a dynamically configurable thread pool in a SpringBoot application by leveraging Nacos as a centralized configuration center, covering dependencies, YAML setup, Java implementation, controller endpoints, and runtime testing.

Architect's Guide
Architect's Guide
Architect's Guide
Building a Dynamic Thread Pool with SpringBoot and Nacos

Background

Thread pool parameters in backend services are often tuned by experience and require service restarts when changed, leading to high operational cost. By moving the thread pool configuration to a platform side and using Nacos as a configuration center, core and maximum thread counts can be adjusted dynamically at runtime.

Dependencies

<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>

YAML Configuration

bootstrap.yml

server:
  port: 8010
# Application name (used as service name in 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

spring:
  profiles:
    active: dev

Bootstrap has higher priority than application.yml, ensuring Nacos configuration is loaded before the application starts.

Nacos Configuration

In the Nacos console a new configuration is created. The Data ID follows the pattern

${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}

, resulting in order-service-dev.yml for this example. The file contains only two entries: core.size and max.size.

Dynamic Thread Pool Implementation

@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 {
        // Initialize thread pool from Nacos config
        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 config change listener
        nacosConfigManager.getConfigService().addListener(
                "order-service-dev.yml",
                nacosConfigProperties.getGroup(),
                new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }

                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        // Config changed, update thread pool
                        System.out.println(configInfo);
                        changeThreadPoolConfig(Integer.parseInt(coreSize), Integer.parseInt(maxSize));
                    }
                });
    }

    /** Print current thread pool status */
    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());
    }

    /** Add tasks to the thread pool */
    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();
                    }
                }
            });
        }
    }

    /** Change core and max size */
    private void changeThreadPoolConfig(int coreSize, int maxSize) {
        threadPoolExecutor.setCorePoolSize(coreSize);
        threadPoolExecutor.setMaximumPoolSize(maxSize);
    }
}

Key annotations: @RefreshScope enables Nacos dynamic refresh. @Value("${max.size}") and @Value("${core.size}") read the parameters from Nacos. nacosConfigManager.getConfigService().addListener registers a listener to apply changes without restarting.

Controller for Observation

@RestController
@RequestMapping("/threadpool")
public class ThreadPoolController {

    @Autowired
    private DynamicThreadPool dynamicThreadPool;

    /** Print current thread pool status */
    @GetMapping("/print")
    public String printThreadPoolStatus() {
        return dynamicThreadPool.printThreadPoolStatus();
    }

    /** Add tasks */
    @GetMapping("/add")
    public String dynamicThreadPoolAddTask(int count) {
        dynamicThreadPool.dynamicThreadPoolAddTask(count);
        return String.valueOf(count);
    }
}

Testing Procedure

Start the application and call http://localhost:8010/threadpool/print – the response shows the initial core and max values defined in Nacos.

Invoke http://localhost:8010/threadpool/add?count=20 to submit 20 tasks. Re‑print the status and observe queued tasks and possible rejection messages.

After several calls, all tasks are rejected. Update the Nacos configuration, setting core.size=50 and max.size=100, then repeat the /add calls. No rejections appear and the printed status reflects the new parameters.

Conclusion

The article provides a straightforward implementation that allows the core and maximum thread counts of a ThreadPoolExecutor to be modified at runtime via Nacos. For production‑grade solutions, refer to Meituan's detailed article on Java thread‑pool practice.

Reference: https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaConfigurationNacosSpringBootThreadPoolExecutorDynamic Thread Pool
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.