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