Thread pool in SpringBoot

Posted by WildcatRudy on Sat, 04 Sep 2021 02:29:57 +0200

During the project two days ago, I wanted to improve the performance optimization of inserting tables, because there are two tables. Insert the old table first, and then insert the new table. More than 10000 data are a little slow

The thread pool ThreadPoolExecutor comes to mind later, and the Spring Boot project is used. The thread pool ThreadPoolTaskExecutor encapsulated by ThreadPoolExecutor provided by Spring can be directly enabled with annotations

Use steps

First create a thread pool Configuration and let Spring Boot load it to define how to create a ThreadPoolTaskExecutor. Use @ Configuration and @ EnableAsync annotations to indicate that this is a Configuration class and a thread pool Configuration class

@Configuration
@EnableAsync
public class ExecutorConfig {
   
     

    private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);

    @Value("${async.executor.thread.core_pool_size}")
    private int corePoolSize;
    @Value("${async.executor.thread.max_pool_size}")
    private int maxPoolSize;
    @Value("${async.executor.thread.queue_capacity}")
    private int queueCapacity;
    @Value("${async.executor.thread.name.prefix}")
    private String namePrefix;

    @Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        logger.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //Configure the number of core threads
        executor.setCorePoolSize(corePoolSize);
        //Configure maximum threads
        executor.setMaxPoolSize(maxPoolSize);
        //Configure queue size
        executor.setQueueCapacity(queueCapacity);
        //Configure the name prefix of threads in the thread pool
        executor.setThreadNamePrefix(namePrefix);

        // Rejection policy: how to handle new tasks when the pool has reached max size
        // CALLER_RUNS: the task is not executed in the new thread, but in the thread of the caller
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //Perform initialization
        executor.initialize();
        return executor;
    }
}

@Value is configured in application.properties. You can refer to the configuration and define it freely  

# Asynchronous thread configuration
# Configure the number of core threads
async.executor.thread.core_pool_size = 5
# Configure maximum threads
async.executor.thread.max_pool_size = 5
# Configure queue size
async.executor.thread.queue_capacity = 99999
# Configure the name prefix of threads in the thread pool
async.executor.thread.name.prefix = async-service-

Create a Service interface, which is the interface of asynchronous threads  

public interface AsyncService {
   
     
    /**
     * Perform asynchronous tasks
     * You can add parameters according to your needs. I'll make a test demonstration here
     */
    void executeAsync();
}

  Implementation class

@Service
public class AsyncServiceImpl implements AsyncService {
   
     
    private static final Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class);

    @Override
    @Async("asyncServiceExecutor")
    public void executeAsync() {
        logger.info("start executeAsync");

        System.out.println("What asynchronous threads do");
        System.out.println("You can perform time-consuming things such as batch insertion here");

        logger.info("end executeAsync");
    }
}

 

Asynchronize the services of the Service layer, and add the annotation @ Async("asyncServiceExecutor") on the executeAsync() method. The asyncServiceExecutor method is the method name in the previous ExecutorConfig.java, indicating that the thread pool entered by the executeAsync method is created by the asyncServiceExecutor method

The next step is to inject the Service in the Controller or where through the annotation @ Autowired

@Autowired
private AsyncService asyncService;

@GetMapping("/async")
public void async(){
    asyncService.executeAsync();
}

 

  Use postmain or other tools to test the request multiple times

 2021-04-16 22:15:47.655  INFO 10516 --- [async-service-5] c.u.d.e.executor.impl.AsyncServiceImpl   : start executeAsync
 What asynchronous threads do
 You can perform time-consuming things such as batch insertion here
2021-04-16 22:15:47.655  INFO 10516 --- [async-service-5] c.u.d.e.executor.impl.AsyncServiceImpl   : end executeAsync
2021-04-16 22:15:47.770  INFO 10516 --- [async-service-1] c.u.d.e.executor.impl.AsyncServiceImpl   : start executeAsync
 What asynchronous threads do
 You can perform time-consuming things such as batch insertion here
2021-04-16 22:15:47.770  INFO 10516 --- [async-service-1] c.u.d.e.executor.impl.AsyncServiceImpl   : end executeAsync
2021-04-16 22:15:47.816  INFO 10516 --- [async-service-2] c.u.d.e.executor.impl.AsyncServiceImpl   : start executeAsync
 What asynchronous threads do
 You can perform time-consuming things such as batch insertion here
2021-04-16 22:15:47.816  INFO 10516 --- [async-service-2] c.u.d.e.executor.impl.AsyncServiceImpl   : end executeAsync
2021-04-16 22:15:48.833  INFO 10516 --- [async-service-3] c.u.d.e.executor.impl.AsyncServiceImpl   : start executeAsync
 What asynchronous threads do
 You can perform time-consuming things such as batch insertion here
2021-04-16 22:15:48.834  INFO 10516 --- [async-service-3] c.u.d.e.executor.impl.AsyncServiceImpl   : end executeAsync
2021-04-16 22:15:48.986  INFO 10516 --- [async-service-4] c.u.d.e.executor.impl.AsyncServiceImpl   : start executeAsync
 What asynchronous threads do
 You can perform time-consuming things such as batch insertion here
2021-04-16 22:15:48.987  INFO 10516 --- [async-service-4] c.u.d.e.executor.impl.AsyncServiceImpl   : end executeAsync

 

It can be found from the above logs that [async Service -] has multiple threads, which have obviously been executed in the thread pool configured by us, and the start and end logs of the controller are printed continuously in each request, indicating that each request is responded quickly, and the time-consuming operations are left to the threads in the thread pool to execute asynchronously;

Although we have used the thread pool, it is not clear how many threads are executing and how many are waiting in the queue? Here, I created a subclass of ThreadPoolTaskExecutor, which will print out the current thread pool health every time a thread is submitted

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @Author: ChenBin
 */
public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
   
     

    private static final Logger logger = LoggerFactory.getLogger(VisiableThreadPoolTaskExecutor.class);

    private void showThreadPoolInfo(String prefix) {
        ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();

        if (null == threadPoolExecutor) {
            return;
        }

        logger.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
                this.getThreadNamePrefix(),
                prefix,
                threadPoolExecutor.getTaskCount(),
                threadPoolExecutor.getCompletedTaskCount(),
                threadPoolExecutor.getActiveCount(),
                threadPoolExecutor.getQueue().size());
    }

    @Override
    public void execute(Runnable task) {
        showThreadPoolInfo("1. do execute");
        super.execute(task);
    }

    @Override
    public void execute(Runnable task, long startTimeout) {
        showThreadPoolInfo("2. do execute");
        super.execute(task, startTimeout);
    }

    @Override
    public Future<?> submit(Runnable task) {
        showThreadPoolInfo("1. do submit");
        return super.submit(task);
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        showThreadPoolInfo("2. do submit");
        return super.submit(task);
    }

    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        showThreadPoolInfo("1. do submitListenable");
        return super.submitListenable(task);
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        showThreadPoolInfo("2. do submitListenable");
        return super.submitListenable(task);
    }
}

  As shown above, the showThreadPoolInfo method prints out the total number of tasks, the number of completed threads, the number of active threads, and the queue size, and then overrides the execute, submit, and other methods of the parent class, where the showThreadPoolInfo method is called, so that each time a task is submitted to the thread pool, the basic information of the current thread pool will be printed to the log;

Modify the asyncServiceExecutor method of ExecutorConfig.java and change threadpooltaskexecutor = new threadpooltaskexecutor() to threadpooltaskexecutor = new visiblethreadpooltaskexecutor()  

@Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        logger.info("start asyncServiceExecutor");
        //Modify here
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //Configure the number of core threads
        executor.setCorePoolSize(corePoolSize);
        //Configure maximum threads
        executor.setMaxPoolSize(maxPoolSize);
        //Configure queue size
        executor.setQueueCapacity(queueCapacity);
        //Configure the name prefix of threads in the thread pool
        executor.setThreadNamePrefix(namePrefix);

        // Rejection policy: how to handle new tasks when the pool has reached max size
        // CALLER_RUNS: the task is not executed in the new thread, but in the thread of the caller
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //Perform initialization
        executor.initialize();
        return executor;
    }

  Start the project test again

 2021-04-16 22:23:30.951  INFO 14088 --- [nio-8087-exec-2] u.d.e.e.i.VisiableThreadPoolTaskExecutor : async-service-, 2. do submit,taskCount [0], completedTaskCount [0], activeCount [0], queueSize [0]
2021-04-16 22:23:30.952  INFO 14088 --- [async-service-1] c.u.d.e.executor.impl.AsyncServiceImpl   : start executeAsync
What asynchronous threads do
You can perform time-consuming things such as batch insertion here
2021-04-16 22:23:30.953  INFO 14088 --- [async-service-1] c.u.d.e.executor.impl.AsyncServiceImpl   : end executeAsync
2021-04-16 22:23:31.351  INFO 14088 --- [nio-8087-exec-3] u.d.e.e.i.VisiableThreadPoolTaskExecutor : async-service-, 2. do submit,taskCount [1], completedTaskCount [1], activeCount [0], queueSize [0]
2021-04-16 22:23:31.353  INFO 14088 --- [async-service-2] c.u.d.e.executor.impl.AsyncServiceImpl   : start executeAsync
What asynchronous threads do
You can perform time-consuming things such as batch insertion here
2021-04-16 22:23:31.353  INFO 14088 --- [async-service-2] c.u.d.e.executor.impl.AsyncServiceImpl   : end executeAsync
2021-04-16 22:23:31.927  INFO 14088 --- [nio-8087-exec-5] u.d.e.e.i.VisiableThreadPoolTaskExecutor : async-service-, 2. do submit,taskCount [2], completedTaskCount [2], activeCount [0], queueSize [0]
2021-04-16 22:23:31.929  INFO 14088 --- [async-service-3] c.u.d.e.executor.impl.AsyncServiceImpl   : start executeAsync
What asynchronous threads do
You can perform time-consuming things such as batch insertion here
2021-04-16 22:23:31.930  INFO 14088 --- [async-service-3] c.u.d.e.executor.impl.AsyncServiceImpl   : end executeAsync
2021-04-16 22:23:32.496  INFO 14088 --- [nio-8087-exec-7] u.d.e.e.i.VisiableThreadPoolTaskExecutor : async-service-, 2. do submit,taskCount [3], completedTaskCount [3], activeCount [0], queueSize [0]
2021-04-16 22:23:32.498  INFO 14088 --- [async-service-4] c.u.d.e.executor.impl.AsyncServiceImpl   : start executeAsync
What asynchronous threads do
You can perform time-consuming things such as batch insertion here
2021-04-16 22:23:32.499  INFO 14088 --- [async-service-4] c.u.d.e.executor.impl.AsyncServiceImpl   : end executeAsync

  Note this line of log:

2021-04-16 22:23:32.496  INFO 14088 --- [nio-8087-exec-7] u.d.e.e.i.VisiableThreadPoolTaskExecutor : async-service-, 2. do submit,taskCount [3], completedTaskCount [3], activeCount [0], queueSize [0]

  This indicates that when submitting a task to the thread pool, the method submit(Callable task) is called. Currently, three tasks have been submitted and completed. At present, there are 0 threads processing tasks, and there are 0 tasks waiting in the queue. The basic situation of the thread pool has been all the way;

Topics: Java Spring Spring Boot