Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Multiple Independent Thread Pools in Spring Boot

Tech 1

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";
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.