Explanation of using java thread pool ExecutorService in spring boot

  • 2021-07-06 10:51:53
  • OfStack

1. Understanding the java thread pool

1.1 Under what circumstances do you use thread pools?

1. The processing time of a single task is relatively short 2. The number of tasks to be dealt with is large

1.2 Benefits of using thread pools:

1. Reduce the time spent creating and destroying threads and the overhead of system resources 2. If the thread pool is not used, it may cause the system to create a large number of threads and consume the system memory

1.3 The thread pool consists of the following four basic components:

1. Thread Pool Manager (ThreadPool): used to create and manage thread pools, including creating thread pools, destroying thread pools and adding new tasks; 2. Worker thread (PoolWorker): The thread in the thread pool is in a waiting state when there is no task and can execute tasks cyclically; 3. Task Interface (Task): The interface that each task must implement for the worker thread to schedule the execution of the task. It mainly specifies the entry of the task, the finishing work after the task is executed, and the execution status of the task; 4. Task queue (taskQueue): used to store unprocessed tasks. Provides a buffer mechanism.

1.4 Core Parameters of Thread Pool

ThreadPoolExecutor has four constructors, the first three of which call the last one (the last one is the most complete)


 public ThreadPoolExecutor(int corePoolSize,
               int maximumPoolSize,
               long keepAliveTime,
               TimeUnit unit,
               BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
       Executors.defaultThreadFactory(), defaultHandler);
  }
  public ThreadPoolExecutor(int corePoolSize,
               int maximumPoolSize,
               long keepAliveTime,
               TimeUnit unit,
               BlockingQueue<Runnable> workQueue,
               ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
       threadFactory, defaultHandler);
  }
  public ThreadPoolExecutor(int corePoolSize,
               int maximumPoolSize,
               long keepAliveTime,
               TimeUnit unit,
               BlockingQueue<Runnable> workQueue,
               RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
       Executors.defaultThreadFactory(), handler);
  }
  //  Call it 
  public ThreadPoolExecutor(//  Number of core threads 
  int corePoolSize, 
               //  Maximum number of threads 
               int maximumPoolSize, 
               //  Idle thread survival time 
               long keepAliveTime, 
               //  Time unit 
               TimeUnit unit, 
               //  Thread queue 
               BlockingQueue<Runnable> workQueue, 
               //  Thread factory  
               ThreadFactory threadFactory,        
               //  Queue Full , Exception handling strategy when the current number of threads has exceeded the maximum number of threads        
               RejectedExecutionHandler handler  ) {
    if (corePoolSize < 0 ||
      maximumPoolSize <= 0 ||
      maximumPoolSize < corePoolSize ||
      keepAliveTime < 0)
      throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
      throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
  }

Main parameters

corePoolSize: Number of core threads

The core thread will survive 1 straight, even if there are no tasks to execute When the number of threads is less than the number of core threads, the thread pool will give priority to creating new threads even if the threads are idle When you set allowCoreThreadTimeout=true (default false), the core thread timed out

maxPoolSize: Maximum number of threads

When the number of threads > = corePoolSize and the task queue is full. The thread pool creates a new thread to process the task When the number of threads = maxPoolSize and the task queue is full, the thread pool will refuse to process the task and throw an exception

keepAliveTime: Thread Idle Time

When the thread idle time reaches keepAliveTime, the thread exits until the number of threads = corePoolSize If allowCoreThreadTimeout=true, until threads count = 0

workQueue: A blocking queue is used to store tasks waiting to be executed. The choice of this parameter is also very important, which will have a significant impact on the running process of the thread pool. Generally speaking, the blocking queue here has the following choices:

ArrayBlockingQueue; LinkedBlockingQueue; SynchronousQueue;

See this article about blocking queues: java blocking queues

threadFactory: Thread factory, mainly used to create threads;

rejectedExecutionHandler: The task rejects the processor, and the task is rejected in two cases:

When the number of threads has reached maxPoolSize, the cut queue is full and new tasks will be rejected When the thread pool is called shutdown (), it will wait for the task in the thread pool to finish executing before shutdown. If a task is submitted between the call to shutdown () and the thread pool's real shutdown, the new task is rejected

When a task is refused to be processed, the thread pool calls rejectedExecutionHandler to process the task. If the default is not set to AbortPolicy, an exception will be thrown. The ThreadPoolExecutor class has several internal implementation classes to handle such situations:

AbortPolicy Drop Tasks, Throw Runtime Exceptions CallerRunsPolicy Execute Tasks DiscardPolicy Ignore, nothing will happen DiscardOldestPolicy kicks out the first task to enter the queue (the last one to execute) from the queue Implement RejectedExecutionHandler interface and customize processor

1.5 Java Thread Pool ExecutorService

Executors. newCachedThreadPool creates a cacheable thread pool. If the length of the thread pool exceeds the processing needs, idle threads can be flexibly recycled, and if there is no recycling, new threads can be created. Executors. newFixedThreadPool creates a fixed-length thread pool that controls the maximum number of threads concurrent, with excess threads waiting in the queue. Executors. newScheduledThreadPool creates a fixed-length thread pool to support timed and periodic task execution. Executors. newSingleThreadExecutor creates a single-threaded thread pool, which executes tasks with only one worker thread, ensuring that all tasks are executed in the specified order (FIFO, LIFO, priority).

Note: Executors is only a factory class, and all its methods return instances of ThreadPoolExecutor and ScheduledThreadPoolExecutor.

1.6 ExecutorService has the following methods of execution

executorService. execute (Runnable); This method receives 1 instance of Runnable and executes asynchronously executorService.submit(Runnable) executorService.submit(Callable) executorService. invokeAny (…) executorService. invokeAll (…)

execute(Runnable)

This method receives 1 Runnable instance and executes asynchronously


executorService.execute(new Runnable() {
public void run() {
  System.out.println("Asynchronous task");
}
});
executorService.shutdown();

submit(Runnable)

The difference between submit (Runnable) and execute (Runnable) is that the former can return one Future object, and through the returned Future object, we can check whether the submitted task has been executed. Please see the following example:


Future future = executorService.submit(new Runnable() {
public void run() {
  System.out.println("Asynchronous task");
}
});
future.get(); //returns null if the task has finished correctly.

submit(Callable)

Similar to submit (Runnable), submit (Callable) returns an Future object, but otherwise, submit (Callable) receives an implementation of Callable, and the call () method in the Callable interface has a return value to return the execution result of the task, while the run () method in the Runnable interface is void and has no return value. Look at the following example:


Future future = executorService.submit(new Callable(){
public Object call() throws Exception {
  System.out.println("Asynchronous Callable");
  return "Callable Result";
}
});
System.out.println("future.get() = " + future.get());

If the task completes, the future. get () method returns the execution result of the Callable task. Note that the future. get () method produces blocking.

invokeAny (…)

The invokeAny (...) method receives a collection of 1 Callable. Executing this method does not return Future, but returns the execution result of 1 of all Callable tasks. This method can't guarantee which task's execution result is returned, but it is one of them anyway.


ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>() {
public String call() throws Exception {
  return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
  return "Task 2";
}
});
callables.add(new Callable<String>() {
  public String call() throws Exception {
  return "Task 3";
}
});
String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();

invokeAll (…)

invokeAll (...), like invokeAny (...), also receives one set of Callable, but after the former is executed, it returns one List of Future, which corresponds to the Future object after each Callable task is executed.


List<Future<String>> futures = executorService.invokeAll(callables);
for(Future<String> future : futures){
System.out.println("future.get = " + future.get());
}
executorService.shutdown();

2. Using java Thread Pool ExecutorService in springBoot

2.1 Usage Configuration of springBoot


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 *  Data collection configuration, the main function is Spring Automatically load at startup 1 A ExecutorService Object .
 * @author Bruce
 * @date 2017/2/22
 * update by Cliff at 2027/11/03
 */
@Configuration
public class ThreadPoolConfig {
  @Bean
  public ExecutorService getThreadPool(){
    return Executors.newFixedThreadPool();
  }
}

2.2 Use


 In @service  Medium injection  ExecutorService  Then you can use it directly. 
  @Autowired
  private ExecutorService executorService;
public void test(){
    executorService.execute(new Runnable() {
      public void run() {
        System.out.println("Asynchronous task");
      }
    });
  }

Summarize


Related articles: