Last week, there was an article about using @Async in Spring Boot for asynchronous tasks and thread pool control: Spring Boot for Asynchronous Calls with @Async: Custom Thread Pools. The ThreadPoolTaskScheduler scheduler thread pool is a scheduler that uses scheduler scheduler to scheduler threads.

Phenomenon of the problem

In the example chapter4-1-3 from the previous article, we defined a thread pool, then wrote three tasks using the @async annotation and specified the thread pool to execute those tasks. In the unit test above, we did not specifically talk about shutdown related problems, so let’s simulate a problem field.

Step 1: As before, we define a ThreadPoolTaskScheduler thread pool:

@SpringBootApplication
public class Application {

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

    @EnableAsync
    @Configuration
    class TaskPoolConfig {

        @Bean("taskExecutor")
        public Executor taskExecutor(a) {
            ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
            executor.setPoolSize(20);
            executor.setThreadNamePrefix("taskExecutor-");
            returnexecutor; }}}Copy the code

Step 2: Modify the asynchronous task to rely on an external resource, such as Redis

@Slf4j
@Component
public class Task {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Async("taskExecutor")
    public void doTaskOne(a) throws Exception {
        log.info("Start on task one.");
        long start = System.currentTimeMillis();
        log.info(stringRedisTemplate.randomKey());
        long end = System.currentTimeMillis();
        log.info("Complete Task 1, Time:" + (end - start) + "毫秒");
    }

    @Async("taskExecutor")
    public void doTaskTwo(a) throws Exception {
        log.info("Start on task two.");
        long start = System.currentTimeMillis();
        log.info(stringRedisTemplate.randomKey());
        long end = System.currentTimeMillis();
        log.info("Complete Task 2, Time:" + (end - start) + "毫秒");
    }

    @Async("taskExecutor")
    public void doTaskThree(a) throws Exception {
        log.info("Start on task three.");
        long start = System.currentTimeMillis();
        log.info(stringRedisTemplate.randomKey());
        long end = System.currentTimeMillis();
        log.info("Complete Task 3, Time:" + (end - start) + "毫秒"); }}Copy the code

Note: The steps of importing dependencies and configuring REDis in POM.xml are omitted

Step 3: Modify the unit test to simulate ShutDown under high concurrency:

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

    @Autowired
    private Task task;

    @Test
    @SneakyThrows
    public void test(a) {

        for (int i = 0; i < 10000; i++) {
            task.doTaskOne();
            task.doTaskTwo();
            task.doTaskThree();

            if (i == 9999) {
                System.exit(0); }}}}Copy the code

Note: The for loop submits tasks to the thread pool defined above. Since it is asynchronous execution, use system.exit (0) to close the program during execution. At this point, since there are tasks executing, you can observe whether the destruction of these asynchronous tasks is in a safe order with other resources in the Spring container.

Step 4: Run the unit test above and we will encounter the following exception.

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:2 (4) ~ [spring - data - redis - 1.8.10. The jar: na] the at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:348) ~ [spring - data - redis - 1.8.10. The jar: na] the at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:129) ~ [spring - data - redis - 1.8.10. The jar: na] the at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:92) ~ [spring - data - redis - 1.8.10. The jar: na] the at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:79) ~ [spring - data - redis - 1.8.10. The jar: na] the at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:194) ~ [spring - data - redis - 1.8.10. The jar: na] the at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:169) ~ [spring - data - redis - 1.8.10. The jar: na] the at org.springframework.data.redis.core.RedisTemplate.randomKey(RedisTemplate.java:781) ~ [spring - data - redis - 1.8.10. The jar: na] at the didispace. Async. Task. DoTaskOne (Task. Java: 26) ~ [classes / : na] the at com.didispace.async.Task$$FastClassBySpringCGLIB$$ca3ff9d6.invoke(<generated>) ~[classes/:na] at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~ [spring - core - 4.3.14. RELEASE. The jar: 4.3.14. RELEASE] at org. Springframework. Aop) framework. CglibAopProxy$CglibMethodInvocationInvokeJoinpoint (CglibAopProxy. Java: 738) ~ [spring aop -- 4.3.14. The jar: 4.3.14. RELEASE] the at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~ [spring aop -- 4.3.14. RELEASE. The jar: 4.3.14. RELEASE] at org. Springframework. Aop) interceptor. AsyncExecutionInterceptorThe $1Call (AsyncExecutionInterceptor. Java: 115) ~ [spring aop -- 4.3.14. The jar: 4.3.14. RELEASE] the at Java. Util. Concurrent. FutureTask. Run (FutureTask. Java: 266) [na: 1.8.0 comes with _151] the at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.accessThe $201(ScheduledThreadPoolExecutor. Java: 180) [na: 1.8.0 comes with _151] at Java. Util. Concurrent. ScheduledThreadPoolExecutor$ScheduledFutureTask. The run (ScheduledThreadPoolExecutor. Java: 293) [na: 1.8.0 comes with _151] the at Java. Util. Concurrent. ThreadPoolExecutor. RunWorker (ThreadPoolExecutor. Java: 1149) [na: 1.8.0 comes with _151] the at java.util.concurrent.ThreadPoolExecutor$Worker. The run (ThreadPoolExecutor. Java: 624) [na: 1.8.0 comes with _151] at Java lang. Thread. The run (748) Thread. Java: [na: 1.8.0 comes with _151] under Caused by: redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool at redis. Clients. Util. Pool. The getResource (53) pool. Java: ~ [jedis - 2.9.0. Jar: na] the at Redis. Clients. Jedis. JedisPool. GetResource (JedisPool. Java: 226) ~ [jedis - 2.9.0. Jar: na] the at Redis. Clients. Jedis. JedisPool. GetResource (JedisPool. Java: 16) ~ [jedis - 2.9.0. Jar: na] the at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:1 (94) ~ [spring - data - redis - 1.8.10. The jar: na]... 19 common frames omitted Caused by: java.lang.InterruptedException: null at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObjectReportInterruptAfterWait (AbstractQueuedSynchronizer. Java: 2014) ~ [na: 1.8.0 comes with _151] the at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObjectAwaitNanos (AbstractQueuedSynchronizer. Java: 2088) ~ [na: 1.8.0 comes with _151] the at org.apache.commons.pool2.impl.LinkedBlockingDeque.pollFirst(LinkedBlockingDeque.java:635) ~ [Commons - pool2-2.4.3. Jar: 2.4.3] the at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:442) ~ [Commons - pool2-2.4.3. Jar: 2.4.3] the 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 (49) Pool. Java: ~ / jedis - 2.9.0. Jar: na... 22 common frames omittedCopy the code

How to solve

Cause analysis,

From the exception information JedisConnectionException: The Redis connection pool was destroyed first, so the Redis connection pool Could not get a resource from the pool. The Redis connection pool Could not get a resource from the pool. So, we conclude that the above implementation is not elegant when the application is closed, so what do we do?

The solution

Spring’s ThreadPoolTaskScheduler provides the configuration to solve the above problem. Simply add the following:

@Bean("taskExecutor")
public Executor taskExecutor(a) {
    ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
    executor.setPoolSize(20);
    executor.setThreadNamePrefix("taskExecutor-");
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setAwaitTerminationSeconds(60);
    return executor;
}
Copy the code

Description: setWaitForTasksToCompleteOnShutdown (true) is the key here, the method used to set the thread pool closed when waiting for all the tasks are completed to destroy other beans, so the destruction of the asynchronous task will precede Redis destruction of the thread pool. At the same time, it is also equipped with setAwaitTerminationSeconds (60), the method is used to set the thread pool task waiting time, if more than this time haven’t destroy force to destroy, to ensure that the application of the last to be closed, and not blocked.

Complete example:

Readers can view chapter4-1-4 projects in the following two warehouses according to their preferences:

  • Github:https://github.com/dyc87112/SpringBoot-Learning/
  • Gitee:https://gitee.com/didispace/SpringBoot-Learning/

If you are interested in these, welcome to star, follow, favorites, forward to give support!