Use and configuration of @Async annotation in Spring

Use and configuration of @Async annotation in Spring

Reference article: https://blog.csdn.net/weixin_42272869/article/details/123082657

1. Use of @Async annotation

In using the spring framework, it is very simple and convenient to implement an asynchronous execution method. Specifically, you only need to add the @EnableAsync annotation to the startup class to enable asynchronous support, and then use the @Async annotation on the method that requires asynchronous processing to perform asynchronous execution.

Note: If you want to execute asynchronously, you cannot directly call the method marked by the @Async annotation in this class in a class. Direct calls in this class will be executed synchronously and will not be executed asynchronously

main startup class

@EnableAsync//Enable asynchronous support, or mark it on the class marked by @Configuration annotation, the effect is the same
@SpringBootApplication
public class ApplicationTest{
  ....
}

Use case: need to be handed over to the spring container management bean

@Component
public class MyAsyncService {

    @Async//Just use asynchronous annotations directly, and the default thread pool is the custom implemented thread pool
    public void testAsync(){
        System.out.println("==== I executed ====");
        System.out.println("MyAsyncService.testAsync() = " + Thread.currentThread().getName());
    }

}

Test execution:

/* Execute tests with SpringBoot */
@SpringBootTest(classes = ApplicationTest.class)
public class ApplicationTest1 {

    @Resource
    MyAsyncService myAsyncService;
  
    @Test
    void testTread(){
        myAsyncService.testAsync();
        System.out.println("Finish.... " );
    }
}

Test Results:

2. Configuration and use of @Async annotation thread pool

1. @Async default thread pool modification

For modifying the default thread pool used by @Async, we can implement the AsyncConfigurer interface and rewrite the getAsyncExecutor() method to provide it with our own defined thread pool

Concrete example:

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * Description: Thread pool configuration class, modify the default thread pool used by @Async annotation
 *
 * @author SXT
 * @version 1.0
 * @date 2022/11/22
 */
//Enable automatic asynchronous annotations and put them together with configuration classes for easy management 
@EnableAsync
@Configuration
@Slf4j
public class AsyncTaskPoolConfig implements AsyncConfigurer {

    /**
     * Used for @Async annotation to get the default thread connection pool
     * @return
     */
    @Override
    public Executor getAsyncExecutor() {
        //This class is provided by Spring, under the org.springframework.scheduling.concurrent package, and is the encapsulation class of the thread pool
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //The thread name prefix in the thread pool
        taskExecutor.setThreadNamePrefix("taskThreadPool-async-");
        //Number of thread pool core threads
        taskExecutor.setCorePoolSize(5);
        //The maximum number of threads in the thread pool
        taskExecutor.setMaxPoolSize(10);
        //The survival time of idle threads in the thread pool, in seconds
        taskExecutor.setKeepAliveSeconds(100);
        //Thread pool rejection policy
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        //Thread pool task team capacity, if not set, the default is Integer.MAX_VALUE,
        // The queue uses LinkedBlockingQueue by default. If the value of queueCapacity <= 0, use SynchronousQueue
        taskExecutor.setQueueCapacity(1000);

        //Whether the core thread in the thread pool allows timeout, the default is false
        taskExecutor.setAllowCoreThreadTimeOut(true);

        //The timeout processing time in the thread pool, in seconds, there is a corresponding method in milliseconds, the default is no timeout
        taskExecutor.setAwaitTerminationSeconds(60);

        //Initialize the thread pool, not less, otherwise it will throw the thread pool is not initialized
        taskExecutor.initialize();
        return taskExecutor;
    }

    /**
     * Unified processing mechanism for thread unhandled exceptions, that is, thread pool exception handler, simple example
     * @return
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        // exception handler function interface class
        return new AsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
                log.error("============ " + throwable.getMessage() + " ===========", throwable);
                log.error("============ " + method.getName() + " ===========", objects);
            }
        };
    }
}

After implementing the getAsyncExecutor() method in the AsyncConfigurer class, when using the @Async annotation for asynchronous execution, the default thread pool used is the thread pool provided by the implementation. The specific usage examples are as follows:

/**
 * Description: Asynchronous method call
 *
 * @author SXT
 * @version 1.0
 * @date 2022/11/23
 */
@Component
public class MyAsyncService {

    @Async//Just use asynchronous annotations directly, and the default thread pool is the custom implemented thread pool
    public void testAsync(){
        System.out.println("==== I executed ====");
        System.out.println("MyAsyncService.testAsync() = " + Thread.currentThread().getName());
    }

}

/* Execute tests with SpringBoot */
@SpringBootTest(classes = ApplicationTest.class)
public class ApplicationTest1 {

    @Resource
    MyAsyncService myAsyncService;
  
    @Test
    void testTread(){
        myAsyncService.testAsync();
        System.out.println("Finish.... " );
    }
}

The test execution results are as follows: truthfully use the thread pool provided by the implementation

2. Custom thread pool (@Async specifies the use of custom thread pool)

Whether you modify the thread pool provided by @Async by default or not, you can specify a specific thread pool for some asynchronous execution methods marked with @Async. If you want to use the specified thread pool, you need to specify it as The @Async annotation specifies the name of the thread pool used (the custom thread pool needs to be handed over to Spring management), which is the name of the bean

Example of custom thread pool: You can see that it is the same as modifying the content of the thread pool provided in the @Async default thread pool. The specific configuration of the thread pool can be set according to requirements

@Configuration
public class CommentConfig {

  /**
     * Custom asynchronous thread pool, if the name of the bean is not specified explicitly, the name of the method will be used as the name of the bean by default
     * @return
     */
    @Bean("asyncTaskPool")
    public Executor asyncTaskPool(){
        //This class is provided by Spring, under the org.springframework.scheduling.concurrent package, and is the encapsulation class of the thread pool
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //The thread name prefix in the thread pool
        executor.setThreadNamePrefix("asyncTaskPool-task-");
        //Number of thread pool core threads
        executor.setCorePoolSize(5);
        //The maximum number of threads in the thread pool
        executor.setMaxPoolSize(10);
        //The survival time of idle threads in the thread pool, in seconds
        executor.setKeepAliveSeconds(100);
        //Thread pool rejection policy
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //Thread pool task team capacity, if not set, the default is Integer.MAX_VALUE,
        // The queue uses LinkedBlockingQueue by default. If the value of queueCapacity <= 0, use SynchronousQueue
        executor.setQueueCapacity(1000);

        //Whether the core thread in the thread pool allows timeout, the default is false
        executor.setAllowCoreThreadTimeOut(true);

        //The timeout processing time in the thread pool, in seconds, there is a corresponding method in milliseconds, the default is no timeout
        executor.setAwaitTerminationSeconds(60);

        //Initialize the thread pool, not less, otherwise it will throw the thread pool is not initialized
        executor.initialize();
        return executor;
    }

}

Example using a custom thread pool:

/**
 * Description: Asynchronous method call
 *
 * @author SXT
 * @version 1.0
 * @date 2022/11/23
 */
@Component
public class MyAsyncService {
		
  	//Specifies the name of the bean that uses the thread pool. If not specified, the thread pool provided by default is used
    //asyncTaskPool is the object name of the thread pool instance managed by Spring
    @Async("asyncTaskPool")
    public void testAsync2(){
        System.out.println("==== I executed ====");
        System.out.println("MyAsyncService.testAsync2() = " + Thread.currentThread().getName());
    }

}


/* Execute tests with SpringBoot */
@SpringBootTest(classes = ApplicationTest.class)
public class ApplicationTest1 {

    @Resource
    MyAsyncService myAsyncService;
  
    @Test
    void testTread2(){
        myAsyncService.testAsync2();
        System.out.println("Finish.... " );
    }
}

The test results are as follows:

expand:

If you want to dynamically configure a custom thread pool according to the configuration file, you can use the following method

Configuration file properties file or yml file

#The properties file is used here for configuration, the same way as the yml file, but the format is different
#You can also change the camel case name to core-size, keep-alive-seconds, and the corresponding entity class is still named after the camel case
asyncTask.pool.coreSize=5
asyncTask.pool.maxPoolSize=10
asyncTask.pool.keepAliveSeconds=60
asyncTask.pool.queueCapacity=1000
asyncTask.pool.timeOutSeconds=60

Define the class to obtain the configuration corresponding to the configuration file

/**
 * Description: Thread pool configuration file entity class
 *
 * @author SXT
 * @version 1.0
 * @date 2022/11/23
 */
//Use @ConfigurationProperties annotation, its class must be handed over to Spring management
@ConfigurationProperties(prefix = "async-task.pool")
@Component
@ToString
@Data
public class ThreadConfig {

    private int coreSize;

    private int maxPoolSize;

    private long keepAliveSeconds;

    private int queueCapacity;

    private long timeOutSeconds;

}

@SpringBootTest(classes = ApplicationTest.class)
public class ApplicationTest1 {

    @Autowired
    ThreadConfig threadConfig;

    @Test
    void testProperties1(){
        System.out.println("threadConfig = " + threadConfig);

    }
}

Test Results:

Tip: Do not write the wrong key in the configuration, otherwise the value will not be obtained

3. The principle of @Async annotation

To be perfected

Tags: Spring

Posted by MNS on Mon, 05 Dec 2022 09:01:34 +0300