The use of thread pool in SpringBoot

1. Quick Start

1. Asynchronous method

In Spring Boot, it is very simple to implement an asynchronous execution method. You only need to add annotations to the project (usually added to the startup class or thread pool configuration class), and add annotations @EnableAsyncto the methods that require asynchronous execution .@Async

Add annotations to the startup class @EnableAsync:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableAsync
@EnableScheduling
@EnableTransactionManagement
@SpringBootApplication
public class RedisApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedisApplication.class, args);
    }

}

Create a Service interface:

public interface AsyncService {
    void simpleAsync();
}

@AsyncImplement the interface and its methods, and add asynchronous annotations  to the methods :

import com.redis.service.AsyncService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class AsyncServiceImpl implements AsyncService {
    @Override
    @Async
    public void simpleAsync() {
        log.info("ThreadName【{}】异步方法开始……", Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("ThreadName【{}】异步方法结束。", Thread.currentThread().getName());
    }
}

Therefore, an asynchronous execution method was implemented and a unit test was written for testing: 

@GetMapping("/selectAll")
    public HashMap<String, Student> selectAll(){
        List<Student> students = studentMapper.selectList(null);
        HashMap<String,Student> map=new HashMap<>();
        for (Student student : students) {
            map.put(student.getSId(),student);
        }

        asyncService.simpleAsync();;
        asyncService.simpleAsync();
        // 等待一段时间让异步方法执行结束
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return map;
    }

Run the unit test and you will see that two asynchronous methods with different thread names are executed alternately:

2. Custom thread pool 

The above asynchronous method is implemented through the thread pool task executor automatically injected in SpringBoot. We have not created any thread pool. In most cases, the automatically injected thread pool does not meet actual needs, and the thread pool needs to be customized according to the actual scenario.

Create a thread pool configuration class ThreadPoolConfig in the project config directory, and write a method to create and initialize a thread pool task executor object:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class ThreadPoolConfig {
    @Bean(name = "threadPool_1")
    public ThreadPoolTaskExecutor threadPool01() {
        // 创建线程池任务执行器对象
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数量
        executor.setCorePoolSize(8);
        // 设置最大线程数量
        executor.setMaxPoolSize(16);
        // 设置阻塞队列容量
        executor.setQueueCapacity(256);
        // 设置线程空闲时间,默认为 60 秒
        executor.setKeepAliveSeconds(60);
        // 设置是否支持回收核心线程,默认为 false
        executor.setAllowCoreThreadTimeOut(false);
        // 设置线程名称前缀,若不设置则根据对象的 beanName 生成
        executor.setThreadNamePrefix("threadPool_1_");
        // 设置线程池拒绝策略,默认为 AbortPolicy,即线程数量达到最大线程数量,且阻塞队列容量已满,再添加任务则抛出异常。
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

@AsyncSpecify the thread pool task executor name in the annotation to use the thread pool to execute asynchronous tasks  :

import com.redis.service.AsyncService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class AsyncServiceImpl implements AsyncService {
    @Override
    @Async(value = "threadPool_1")
    public void simpleAsync() {
        log.info("ThreadName【{}】异步方法开始……", Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("ThreadName【{}】异步方法结束。", Thread.currentThread().getName());
    }
}

After specifying the thread pool, re-execute the unit test and find that the thread name prefix is ​​the value set in the configuration, indicating that a custom thread pool is used to perform asynchronous tasks: 

3. Things to note

  • @AsyncAnnotation is a method that asynchronously executes the annotation modification through the aspect proxy. Therefore, asynchronous can only be achieved by injecting the Bean object and directly calling it. Asynchronous calling cannot be achieved within the class .
  • SpringBoot will automatically inject the thread pool task executor object only when no executor object is injected. As long as any thread pool task executor object is manually injected, SpringBoot will no longer automatically inject it.
  • When manually injecting multiple thread pool task executor objects, if @Asyncthe annotation does not specify a name, a thread pool task executor object will be randomly selected to execute the asynchronous task, so it is @Asyncbest to specify a name when adding annotations, or execute it in a thread pool task If you add annotations when injecting the host object @Primary, all asynchronous tasks without specified names will be executed through this thread pool.
  • import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    import java.util.concurrent.ThreadPoolExecutor;
    
    @Configuration
    public class ThreadPoolConfig {
        @Primary
        @Bean(name = "threadPool_1")
        public ThreadPoolTaskExecutor threadPool01() {
            // 创建线程池任务执行器对象
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            // 设置核心线程数量
            executor.setCorePoolSize(8);
            // 设置最大线程数量
            executor.setMaxPoolSize(16);
            // 设置阻塞队列容量
            executor.setQueueCapacity(256);
            // 设置线程空闲时间,默认为 60 秒
            executor.setKeepAliveSeconds(60);
            // 设置是否支持回收核心线程,默认为 false
            executor.setAllowCoreThreadTimeOut(false);
            // 设置线程名称前缀,若不设置则根据对象的 beanName 生成
            executor.setThreadNamePrefix("threadPool_1_");
            // 设置线程池拒绝策略,默认为 AbortPolicy,即线程数量达到最大线程数量,且阻塞队列容量已满,再添加任务则抛出异常。
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
            // 初始化
            executor.initialize();
            return executor;
        }
    }

2. ThreadPoolTaskExecutor thread pool parameters 

When customizing the thread pool, you can configure different thread pool parameters according to different business scenarios to achieve different effects.

1. Number of core threads

The fixed number of threads in the thread pool. If the number of threads currently running in the thread pool is less than the number of core threads, when an asynchronous task is added, a thread will immediately execute the task. The default number of core threads of ThreadPoolTaskExecutor is 1 .
ThreadPoolTaskExecutor sets the number of core threads:

public void setCorePoolSize(int corePoolSize) {
    synchronized(this.poolSizeMonitor) {
        this.corePoolSize = corePoolSize;
        if (this.threadPoolExecutor != null) {
            this.threadPoolExecutor.setCorePoolSize(corePoolSize);
        }
    }
}

ThreadPoolTaskExecutor gets the number of core threads:

public int getCorePoolSize() {
    synchronized(this.poolSizeMonitor) {
        return this.corePoolSize;
    }
}

2. Queue capacity

When the number of active threads in the thread pool reaches the number of core threads and you continue to add asynchronous tasks, the new tasks will enter the blocking queue. The queue capacity is the number of tasks that can be accommodated in the blocking queue. The default queue capacity of ThreadPoolTaskExecutor is 2147483647 (int maximum value) .
ThreadPoolTaskExecutor sets queue capacity:

public void setQueueCapacity(int queueCapacity) {
    this.queueCapacity = queueCapacity;
}

Through createQueue in ThreadPoolTaskExecutor, we can know that when the incoming queueCapacity is greater than 0, the blocking queue is a LinkedBlockingQueue object with a specified capacity, otherwise it is a SynchronousQueue object.

protected BlockingQueue<Runnable> createQueue(int queueCapacity) {
    return (BlockingQueue)(queueCapacity > 0 ? new LinkedBlockingQueue(queueCapacity) : new SynchronousQueue());
}

 

 3. Maximum number of threads

When the number of active threads in the thread pool reaches the number of core threads and the blocking queue capacity is full, when you continue to add asynchronous tasks, if the number of active threads has not reached the maximum number of threads, new threads will be created to execute the asynchronous tasks in the queue. ThreadPoolTaskExecutor's default maximum number of threads is 2147483647 (int maximum value) .
ThreadPoolTaskExecutor sets the maximum number of threads:

public void setMaxPoolSize(int maxPoolSize) {
    synchronized(this.poolSizeMonitor) {
        this.maxPoolSize = maxPoolSize;
        if (this.threadPoolExecutor != null) {
            this.threadPoolExecutor.setMaximumPoolSize(maxPoolSize);
        }
    }
}

ThreadPoolTaskExecutor gets the maximum number of threads:

public int getMaxPoolSize() {
   synchronized(this.poolSizeMonitor) {
       return this.maxPoolSize;
   }
}

4. Thread idle time

The time for threads outside the core thread to survive when idle. The default survival time is 60 seconds .
ThreadPoolTaskExecutor sets the thread idle time:

public void setKeepAliveSeconds(int keepAliveSeconds) {
    synchronized(this.poolSizeMonitor) {
        this.keepAliveSeconds = keepAliveSeconds;
        if (this.threadPoolExecutor != null) {
            this.threadPoolExecutor.setKeepAliveTime((long)keepAliveSeconds, TimeUnit.SECONDS);
        }
    }
}

ThreadPoolTaskExecutor gets the thread idle time:

public int getKeepAliveSeconds() {
    synchronized(this.poolSizeMonitor) {
        return this.keepAliveSeconds;
    }
}

5. Is it allowed to destroy core threads?

ThreadPoolTaskExecutor does not allow the core thread to be destroyed by default, that is, the core thread will not be destroyed when its idle time reaches the set value. It can be set via the following methods:

public void setAllowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) {
    this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;
}

6. Thread name prefix

public void setThreadNamePrefix(@Nullable String threadNamePrefix) {
    super.setThreadNamePrefix(threadNamePrefix);
    this.threadNamePrefixSet = true;
}

7. Rejection strategy

When the number of asynchronous tasks in the thread pool reaches the blocking queue capacity and the number of active threads reaches the maximum number of threads, continuing to add asynchronous tasks will trigger the rejection policy.
Four rejection strategies:

  • AbortPloicy ( default ): throws an exception
  • CallerRunsPolicy: Tasks are executed by the caller
  • DiscardPolicy: Discard excess tasks
  • DiscardOldestPolicy: Discard the oldest task in the blocking queue

ThreadPoolTaskExecutor sets the deny policy:

public void setRejectedExecutionHandler(@Nullable RejectedExecutionHandler rejectedExecutionHandler) {
    this.rejectedExecutionHandler = (RejectedExecutionHandler)(rejectedExecutionHandler != null ? rejectedExecutionHandler : new AbortPolicy());
}

Guess you like

Origin blog.csdn.net/weixin_55127182/article/details/132405990