Implementing Multiple Independent Thread Pools in Spring Boot
Thread Pool Constant Definition
/**
* Thread pool global naming and configuration key constants
*/
public class ThreadPoolMetaConstants {
/**
* Naming prefix for user business thread pool
*/
public static final String USER_POOL_PREFIX = "user-biz-thread";
/**
* Naming prefix for school business thread pool
*/
public static final String SCHOOL_POOL_PREFIX = "school-biz-thread";
/**
* Suffix for thread pool Spring bean names
*/
public static final String POOL_BEAN_SUFFIX = "-task-executor-bean";
/**
* Suffix for running thread name identifiers
*/
public static final String TASK_THREAD_SUFFIX = "-pool-worker-";
/**
* Configuration key for core pool size
*/
public static final String CORE_POOL_SIZE_KEY = "corePoolSize";
/**
* Configuration key for maximum pool size
*/
public static final String MAX_POOL_SIZE_KEY = "maxPoolSize";
/**
* Configuration key for idle thread keep-alive time (seconds)
*/
public static final String KEEP_ALIVE_SEC_KEY = "keepAliveSeconds";
/**
* Configuration key for blocking queue capacity
*/
public static final String QUEUE_CAPACITY_KEY = "queueCapacity";
}
Abstract Thread Pool Configuration Base Class
import lombok.Data;
/**
* Abstract base class for thread pool parameter configuration
*/
@Data
public abstract class BaseThreadPoolProperties {
private int corePoolSize;
private int maxPoolSize;
private int keepAliveSeconds;
private int queueCapacity;
}
Application Configuration Properties
Add the following configurations to application.properties:
thread-pool.user-biz-thread.corePoolSize=2
thread-pool.user-biz-thread.maxPoolSize=4
thread-pool.user-biz-thread.keepAliveSeconds=180
thread-pool.user-biz-thread.queueCapacity=16
thread-pool.school-biz-thread.corePoolSize=3
thread-pool.school-biz-thread.maxPoolSize=6
thread-pool.school-biz-thread.keepAliveSeconds=90
thread-pool.school-biz-thread.queueCapacity=32
Business-Specific Thread Pool Property Classes
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* User business thread pool parameter mapping class
*/
@Data
@Component
@ConfigurationProperties(prefix = "thread-pool.user-biz-thread")
public class UserBizThreadPoolProps extends BaseThreadPoolProperties {
private final String threadNamePrefix = ThreadPoolMetaConstants.USER_POOL_PREFIX + ThreadPoolMetaConstants.TASK_THREAD_SUFFIX;
}
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* School business thread pool parameter mapping class
*/
@Data
@Component
@ConfigurationProperties(prefix = "thread-pool.school-biz-thread")
public class SchoolBizThreadPoolProps extends BaseThreadPoolProperties {
private final String threadNamePrefix = ThreadPoolMetaConstants.SCHOOL_POOL_PREFIX + ThreadPoolMetaConstants.TASK_THREAD_SUFFIX;
}
Thread Pool Bean Registration
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.RejectedExecutionHandler;
/**
* Thread pool instance configuration class
*/
@Configuration
@EnableAsync
@Slf4j
public class ThreadPoolBeanConfig {
private final UserBizThreadPoolProps userPoolProps;
private final SchoolBizThreadPoolProps schoolPoolProps;
public ThreadPoolBeanConfig(UserBizThreadPoolProps userPoolProps, SchoolBizThreadPoolProps schoolPoolProps) {
this.userPoolProps = userPoolProps;
this.schoolPoolProps = schoolPoolProps;
}
@Bean(name = ThreadPoolMetaConstants.USER_POOL_PREFIX + ThreadPoolMetaConstants.POOL_BEAN_SUFFIX)
public ThreadPoolTaskExecutor userBizTaskExecutor() {
RejectedExecutionHandler userRejectHandler = (runnable, executor) ->
log.warn("User business thread pool queue exhausted, execute custom rejection policy for current task");
return buildTaskExecutor(userPoolProps, userPoolProps.getThreadNamePrefix(), userRejectHandler);
}
@Bean(name = ThreadPoolMetaConstants.SCHOOL_POOL_PREFIX + ThreadPoolMetaConstants.POOL_BEAN_SUFFIX)
public ThreadPoolTaskExecutor schoolBizTaskExecutor() {
RejectedExecutionHandler schoolRejectHandler = (runnable, executor) ->
log.warn("School business thread pool queue exhausted, execute custom rejection policy for current task");
return buildTaskExecutor(schoolPoolProps, schoolPoolProps.getThreadNamePrefix(), schoolRejectHandler);
}
private ThreadPoolTaskExecutor buildTaskExecutor(BaseThreadPoolProperties poolProps, String threadNamePrefix, RejectedExecutionHandler rejectHandler) {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(poolProps.getCorePoolSize());
taskExecutor.setMaxPoolSize(poolProps.getMaxPoolSize());
taskExecutor.setKeepAliveSeconds(poolProps.getKeepAliveSeconds());
taskExecutor.setQueueCapacity(poolProps.getQueueCapacity());
taskExecutor.setThreadNamePrefix(threadNamePrefix);
taskExecutor.setRejectedExecutionHandler(rejectHandler);
taskExecutor.initialize();
return taskExecutor;
}
}
Thread Pool Usage Example
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
@Service
public class AsyncTaskDemoService {
private final ThreadPoolTaskExecutor userBizExecutor;
private final ThreadPoolTaskExecutor schoolBizExecutor;
public AsyncTaskDemoService(
@Qualifier(ThreadPoolMetaConstants.USER_POOL_PREFIX + ThreadPoolMetaConstants.POOL_BEAN_SUFFIX) ThreadPoolTaskExecutor userBizExecutor,
@Qualifier(ThreadPoolMetaConstants.SCHOOL_POOL_PREFIX + ThreadPoolMetaConstants.POOL_BEAN_SUFFIX) ThreadPoolTaskExecutor schoolBizExecutor) {
this.userBizExecutor = userBizExecutor;
this.schoolBizExecutor = schoolBizExecutor;
}
public void triggerUserAsyncTask() {
userBizExecutor.execute(() -> {
try {
// Simulate business processing delay
Thread.sleep(4000);
System.out.printf("%s completed user business task execution%n", Thread.currentThread().getName());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("User task interrupted", e);
}
});
}
public void triggerSchoolAsyncTask() {
schoolBizExecutor.execute(() -> {
try {
// Simulate business processing delay
Thread.sleep(2500);
System.out.printf("%s completed school business task execution%n", Thread.currentThread().getName());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("School task interrupted", e);
}
});
}
public String fetchThreadPoolMetrics() {
return String.format("User Biz Thread Pool: coreSize=%d, maxSize=%d, keepAlive=%ds%nSchool Biz Thread Pool: coreSize=%d, maxSize=%d, keepAlive=%ds",
userBizExecutor.getCorePoolSize(), userBizExecutor.getMaxPoolSize(), userBizExecutor.getKeepAliveSeconds(),
schoolBizExecutor.getCorePoolSize(), schoolBizExecutor.getMaxPoolSize(), schoolBizExecutor.getKeepAliveSeconds());
}
}
Test Endpoint
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/async-demo")
public class ThreadPoolTestController {
private final AsyncTaskDemoService asyncTaskDemoService;
public ThreadPoolTestController(AsyncTaskDemoService asyncTaskDemoService) {
this.asyncTaskDemoService = asyncTaskDemoService;
}
@GetMapping("/run-tasks")
public String runTestTasks() {
asyncTaskDemoService.triggerUserAsyncTask();
asyncTaskDemoService.triggerSchoolAsyncTask();
return "Task submission succeeded";
}
}