Implementing a Dynamic Thread Pool with Nacos in Spring Boot
This article explains how to externalize and dynamically adjust a Spring Boot thread pool's core and maximum sizes using Nacos as a configuration center, allowing runtime changes without service restarts and demonstrating the setup, code, and testing procedures.
In backend development, thread pool parameters are often configured statically, which requires service restarts and lacks flexibility. This article demonstrates how to externalize the core and maximum thread counts to Nacos and dynamically adjust them at runtime without restarting the service.
1. 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>2. YAML configuration files
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: devThe bootstrap.yml has higher priority than application.yml , ensuring Nacos configuration is loaded before the application starts.
3. Nacos configuration
In the Nacos console a new configuration is created with Data ID following the pattern ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension} , e.g., order-service-dev.yml , containing the core and max thread size values.
4. DynamicThreadPool implementation and Nacos listener
@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 values
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!");
}
});
// Listen for Nacos config changes
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));
}
});
}
/** 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(() -> {
try {
System.out.println(finalI);
Thread.sleep(10000);
} catch (InterruptedException e) { e.printStackTrace(); }
});
}
}
/** Update 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("${core.size}") and @Value("${max.size}") read the thread pool parameters from Nacos.
nacosConfigManager.getConfigService().addListener monitors configuration changes and updates the pool in real time.
5. Controller for testing
@RestController
@RequestMapping("/threadpool")
public class ThreadPoolController {
@Autowired
private DynamicThreadPool dynamicThreadPool;
/** Print 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);
}
}6. Testing
Start the application and visit http://localhost:8010/threadpool/print to see the current pool configuration. Use http://localhost:8010/threadpool/add?count=20 to add tasks, then print the status again. Adjust the core and max sizes in Nacos (e.g., core=50, max=100) and observe that new tasks are no longer rejected, confirming the dynamic update works.
Conclusion
The article provides a simple implementation of a dynamic thread pool whose core and maximum thread counts can be changed at runtime via Nacos, illustrating the essential annotations, configuration files, listener logic, and testing steps. For production use, further enhancements such as monitoring, alerting, or using more advanced libraries like Hippo4J can be added.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.