Preface
I have previously shared an article about using @Async to implement asynchronous tasks and thread pool control in Spring Boot: "Spring Boot uses @Async to implement asynchronous calls: Custom thread pool". Since I have recently discovered many problems caused by not handling asynchronous tasks correctly, I will continue to talk about the elegant closing of thread pools, mainly for ThreadPoolTaskScheduler thread pools.
Problem phenomenon
In the example Chapter 4-1-3 of the previous article, we defined a thread pool, and then wrote 3 tasks using the @Async annotation, and specified the thread pool used by these tasks to execute. In the unit test above, we did not specifically talk about shutdown-related issues. Let’s simulate a problem and come up with it on site.
Step 1: As before, we define a ThreadPoolTaskScheduler thread pool:
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @EnableAsync @Configuration class TaskPoolConfig { @Bean("taskExecutor") public Executor taskExecutor() { ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler(); executor.setPoolSize(20); executor.setThreadNamePrefix("taskExecutor-"); return executor; } }}Step 2: Reform the previous asynchronous task and make it rely on an external resource, such as Redis
@Slf4j@Componentpublic class Task { @Autowired private StringRedisTemplate stringRedisTemplate; @Async("taskExecutor") public void doTaskOne() throws Exception { log.info("Start task one"); long start = System.currentTimeMillis(); log.info(stringRedisTemplate.randomKey()); long end = System.currentTimeMillis(); log.info("Complete task one, time taken: " + (end - start) + "milliseconds"); } @Async("taskExecutor") public void doTaskTwo() throws Exception { log.info("Start task 2"); long start = System.currentTimeMillis(); log.info(stringRedisTemplate.randomKey()); long end = System.currentTimeMillis(); log.info("Complete task 2, time taken: " + (end - start) + "milliseconds"); } @Async("taskExecutor") public void doTaskThree() throws Exception { log.info("Start Task Three"); long start = System.currentTimeMillis(); log.info(stringRedisTemplate.randomKey()); long end = System.currentTimeMillis(); log.info("Complete Task Three, time-consuming: " + (end - start) + "milliseconds"); }}Note: The steps to introduce dependencies and configure redis in pom.xml are omitted here
Step 3: Modify the unit test and simulate the ShutDown situation in high concurrency:
@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTestpublic class ApplicationTests { @Autowired private Task task; @Test @SneakyThrows public void test() { for (int i = 0; i < 10000; i++) { task.doTaskOne(); task.doTaskTwo(); task.doTaskThree(); if (i == 9999) { System.exit(0); } } }}Note: By looping through the for loop, you submit tasks to the thread pool defined above. Since it is executed asynchronously, during the execution, use System.exit(0) to close the program. At this time, since there are tasks being executed, you can observe whether the destruction of these asynchronous tasks and the order of other resources in the Spring container is safe.
Step 4: Run the unit test above and we will encounter the following exception content.
org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:204) ~[spring-data-redis-1.8.10.RELEASE.jar:na] at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:348) ~[spring-data-redis-1.8.10.RELEASE.jar:na] at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:129) ~[spring-data-redis-1.8.10.RELEASE.jar:na] at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:92) ~[spring-data-redis-1.8.10.RELEASE.jar:na] at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:79) ~[spring-data-redis-1.8.10.RELEASE.jar:na] at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:194) ~[spring-data-redis-1.8.10.RELEASE.jar:na] at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:169) ~[spring-data-redis-1.8.10.RELEASE.jar:na] at org.springframework.data.redis.core.RedisTemplate.randomKey(RedisTemplate.java:781) ~[spring-data-redis-1.8.10.RELEASE.jar:na] at com.didispace.async.Task.doTaskOne(Task.java:26) ~[classes/:na] at com.didispace.async.Task$$FassClassBySpringCGLIB$$ca3ff9d6.invoke(<generated>) ~[classes/:na] at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.14.RELEASE.jar:4.3.14.RELEASE] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738) ~[spring-aop-4.3.14.RELEASE.jar:4.3.14.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.14.RELEASE.jar:4.3.14.RELEASE] at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:115) ~[spring-aop-4.3.14.RELEASE.jar:4.3.14.RELEASE] at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_151] at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_151] at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.8.0_151] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_151] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_151] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_151]Caused by: redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool at redis.clients.util.Pool.getResource(Pool.java:53) ~[jedis-2.9.0.jar:na] at redis.clients.jedis.JedisPool.getResource(JedisPool.java:226) ~[jedis-2.9.0.jar:na] at redis.clients.jedis.JedisPool.getResource(JedisPool.java:16) ~[jedis-2.9.0.jar:na] at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:194) ~[spring-data-redis-1.8.10.RELEASE.jar:na] ... 19 common frames omittedCaused by: java.lang.InterruptedException: null at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014) ~[na:1.8.0_151] at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2088) ~[na:1.8.0_151] at org.apache.commons.pool2.impl.LinkedBlockingDequ.pollFirst(LinkedBlockingDequ.java:635) ~[commons-pool2-2.4.3.jar:2.4.3] at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:442) ~[commons-pool2-2.4.3.jar:2.4.3] at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:361) ~[commons-pool2-2.4.3.jar:2.4.3] at redis.clients.util.Pool.getResource(Pool.java:49) ~[jedis-2.9.0.jar:na] ... 22 common frames omitted
How to solve it
Cause analysis
From the exception息JedisConnectionException: Could not get a resource from the pool , it is easy for us to think that the asynchronous task is still executing when the application is closed. Since the Redis connection pool is destroyed first, the above error is reported in the asynchronous task to access Redis. So, we concluded that the above implementation method is not elegant when the application is closed, so what should we do?
Solution
It is very simple to solve the above problem. Spring's ThreadPoolTaskScheduler provides us with relevant configurations, just add the following settings:
@Bean("taskExecutor")public Executor taskExecutor() { ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler(); executor.setPoolSize(20); executor.setThreadNamePrefix("taskExecutor-"); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setAwaitTerminationSeconds(60); return executor;} Note: setWaitForTasksToCompleteOnShutdown(true) This method is the key here. It is used to set the thread pool to wait for all tasks to complete before continuing to destroy other beans. In this way, the destruction of these asynchronous tasks will be preceded by the destruction of the Redis thread pool. At the same time, setAwaitTerminationSeconds(60) is also set here. This method is used to set the waiting time of tasks in the thread pool. If it exceeds this time, it will be forced to destroy it before it is destroyed to ensure that the application can be closed in the end rather than blocked.
Complete example:
Readers can choose the following two repositories to view Chapter4-1-4 projects according to their preferences:
Github: https://github.com/dyc87112/SpringBoot-Learning/
Gitee: https://gitee.com/diidispace/SpringBoot-Learning/
Local download: http://xiazai.VeVB.COM/201805/yuanma/SpringBoot-Learning(VeVB.COM).rar
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.