Spring Boot implements asynchronous invocation using @Async: custom thread pool

Posted by syed on Sat, 14 Sep 2019 10:49:36 +0200

In the previous Spring Book article, we introduced how to use the @Async annotation to implement asynchronous invocation in Spring Boot by using @Async. However, the control of these asynchronous executions is our basic skill to ensure our own application health.

In this article, we will learn how to control the concurrency of asynchronous calls by customizing thread pools.

The examples in this article can be modified on the basis of previous examples, or we can try to create a completely new Spring Book project.

Defining thread pools

The first step is to define a thread pool in the SpringBook main class, such as:

@Configuration
@EnableAsync
public class TaskConfig {
    @Bean("taskExecutor")
    public Executor taskExecutor(){
        ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor();
        poolExecutor.setCorePoolSize(10);
        poolExecutor.setMaxPoolSize(20);
        poolExecutor.setKeepAliveSeconds(60);
        poolExecutor.setQueueCapacity(200);
        poolExecutor.setThreadNamePrefix("zerahTask-");
        poolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return poolExecutor;
    }
}

Above we created a thread pool by using ThreadPoolTaskExecutor and set the following parameters:

  • Number of Core Threads 10: Number of threads initialized at thread pool creation
  • Maximum number of threads 20: Maximum number of threads in the thread pool. Threads that exceed the number of core threads are requested only after the buffer queue is full
  • Buffer queue 200: Queues used to buffer tasks
  • Allow threads to be idle for 60 seconds: Threads that exceed core threads will be destroyed when idle time arrives
  • The prefix of thread pool name: Once set, it is convenient for us to locate the thread pool where the processing task is located.
  • Thread pool's processing strategy for rejected tasks: Caller Runs Policy is adopted here. When the thread pool is not capable of processing, the strategy will run rejected tasks directly in the calling thread of the execute method; if the execution program is closed, the task will be discarded.

2. Using thread pools

After defining the thread pool, how can we let the execution task of the asynchronous call run with the resources in the thread pool? The method is very simple. We just need to specify the thread pool name in the @Async annotation, such as:

@Slf4j
@Component
public class Task {

    public static Random random = new Random();

    @Async("taskExecutor")
    public void doTaskOne() throws Exception {
        log.info("Start Task 1");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Complete Task 1, time-consuming:" + (end - start) + "Millisecond");
    }

    @Async("taskExecutor")
    public void doTaskTwo() throws Exception {
        log.info("Start Task 2");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Complete Task 2, time-consuming:" + (end - start) + "Millisecond");
    }

    @Async("taskExecutor")
    public void doTaskThree() throws Exception {
        log.info("Start Task 3");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Complete Task 3, time-consuming:" + (end - start) + "Millisecond");
    }

}

Unit Testing

Finally, let's write a unit test to verify it.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class ApplicationTests {

    @Autowired
    private Task task;

    @Test
    public void test() throws Exception {

        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();

        Thread.currentThread().join();
    }

}

Executing the unit test above, we can see in the console that all the output thread names begin with the thread pool prefix we defined earlier, which indicates that our experiment using thread pool to perform asynchronous tasks has been successful!

2019-09-14 16:21:00.366 INFO 10724 - [Zerah Task-3] com.zerah.async.async.ThreadAsyncTask: Start Task 3
 2019-09-14 16:21:00.366 INFO 10724 - [Zerah Task-1] com.zerah.async.async.ThreadAsyncTask: Start Task 1
 2019-09-14 16:21:00.366 INFO 10724 - [Zerah Task-2] com.zerah.async.async.ThreadAsyncTask: Start Task 2
 2019-09-14 16:21:01.037 INFO 10724 - [Zerah Task-1] com.zerah.async.async.ThreadAsyncTask: Task 1, 671 MS
 2019-09-14 16:21:05.133 INFO 10724 - [Zerah Task-2] com.zerah.async.async.ThreadAsyncTask: Task 2, 4767 milliseconds
 2019-09-14 16:21:08.832 INFO 10724 - [Zerah Task-3] com.zerah.async.async.ThreadAsyncTask: Task 3, 8466 milliseconds

Thread pool rejection strategy

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

The Rejected Execution Handler here is the upper interface of the rejection policy:

  • RejectedExecutionHandler
    • Discard Oldest Policy discards the oldest task in the queue
    • AbortPolicy throws exceptions
    • CallerRunsPolicy assigns tasks to calling threads for execution
    • DiscardPolicy Discards

At the same time, the four above are static internal classes of ThreadPoolExecutor.

Thread pool rejection policy:

http://www.voidcn.com/article/p-berrqtbx-brq.html

https://juejin.im/post/5d08f6cf518825684b589422

https://blog.csdn.net/u010412719/article/details/52132613

crossoverJie : https://juejin.im/post/5b5e5fa96fb9a04fb900e1ce

Topics: Spring