Spring's @ Async annotation implements asynchronous methods

Posted by rnintulsa on Wed, 09 Feb 2022 12:45:03 +0100

@Async annotation

Spring 3 began to provide @ Async annotation, which can be marked on methods or classes, so as to facilitate the asynchronous invocation of methods. The caller will return immediately when calling the asynchronous method, and the actual execution of the method will be submitted to the thread execution in the specified thread pool.

@Async considerations:

  • @When Async is marked on a class, it means that all methods of the class are asynchronous methods.
  • @Async annotated methods must be called through dependency injection (because they must be called through proxy objects). They cannot be called directly through this object, otherwise they will not take effect.

@Async usage example

1. Enable @ Async in Spring

Create a configuration class and add @ EnableAsync annotation

@Configuration
@EnableAsync
public class SpringAsyncConfig{}

2. Use @ Async

Create asynchronous method execution class

@Service
public class AsyncService {
    // Asynchronous method with no return value
    @Async
    public void noReturnMethod() {
        String tName = Thread.currentThread().getName();
        System.out.println("current thread name : " + tName);
        System.out.println("noReturnMethod end");
    }

    // Asynchronous method with return value
    @Async
    public Future<String> withReturnMethod() {
        String tName = Thread.currentThread().getName();
        System.out.println("current thread name : " + tName);
        return new AsyncResult<>("aaa");
    }
}

Create a class that calls an asynchronous method

@RestController
@RequestMapping("/api/async/test/")
public class AsyncController {
    @Autowired
    AsyncService asyncService;

    // No return value
    @GetMapping("/noReturn")
    public String noReturn() {
        asyncService.noReturnMethod();
        return "success";
    }

    // There is a return value
    @GetMapping("/withReturn")
    public String withReturn() {
        Future<String> future = asyncService.withReturnMethod();
        try {
            String res = future.get();// Block get return value
            System.out.println("res = " + res);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        return "success";
    }
}

Spring defined thread pool class

Spring has defined the following thread pool classes:

  • SimpleAsyncTaskExecutor: it is not a real thread pool. This class does not reuse threads. By default, a new thread will be created for each call.
  • SyncTaskExecutor: this class does not implement asynchronous call, but a synchronous operation. Only applicable where multithreading is not required.
  • ConcurrentTaskExecutor: the adapter class of Executor, which is not recommended. Consider using this class only if the ThreadPoolTaskExecutor does not meet the requirements.
  • SimpleThreadPoolTaskExecutor: is the class of SimpleThreadPool of quartz. This class is required only if the thread pool is used by both quartz and non quartz.
  • ThreadPoolTaskExecutor: most commonly used, recommended. Its essence is Java util. concurrent. Packaging of ThreadPoolExecutor.

Configure custom thread pool

Default thread pool for asynchronous methods

The @ EnableAsync annotation contains the following notes:

By default, Spring will be searching for an associated thread pool definition:either a unique {@link org.springframework.core.task.TaskExecutor} bean in the context,or an {@link java.util.concurrent.Executor} bean named "taskExecutor" otherwise. Ifneither of the two is resolvable, a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}will be used to process async method invocations.

Translated as:

Spring will first find the default thread pool as an asynchronous method in the following two ways:
1. Find a unique bean of type TaskExecutor
2. Or a Bean of Executor type named "taskExecutor".
If neither of the above methods is found, the SimpleAsyncTaskExecutor is used as the default thread pool for asynchronous methods

The SimpleAsyncTaskExecutor thread pool executes the asynchronous methods marked with @ Async. Since the thread pool will not reuse threads, it is recommended to use a custom thread pool in the project.

Configure asynchronous method default custom thread pool

There are several ways to configure @ Async's default thread pool:

  1. Re implement the interface AsyncConfigurer
  2. Inherit AsyncConfigurerSupport
  3. Customize a bean of TaskExecutor type.
  4. Customize a Bean of Executor type named "taskExecutor".

The following shows how to configure the default custom thread pool through mode 1 (re implement the interface AsyncConfigurer):

// The configuration class implements the getAsyncExecutor() method of the AsyncConfigurer interface
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(3);//Core pool size
        executor.setMaxPoolSize(6);//Maximum number of threads
        executor.setKeepAliveSeconds(60);//Thread idle time
        executor.setQueueCapacity(10);//Queue degree
        executor.setThreadNamePrefix("my-executor1-");//Thread prefix name
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//Configure rejection policy
        executor.setAllowCoreThreadTimeOut(true);// Allow core threads to be destroyed
        executor.initialize();
        return executor;
    }
}

Different asynchronous methods configure different thread pools

Sometimes asynchronous methods with different functions need to configure different thread pools, which can be realized by specifying the name of the thread pool on @ Async

@Configuration
public class ExecutorConfig {
    @Bean("customExecutor-1")// Custom thread pool 1
    public Executor customExecutor1() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(3);//Core pool size
        executor.setMaxPoolSize(6);//Maximum number of threads
        executor.setKeepAliveSeconds(60);//Thread idle time
        executor.setQueueCapacity(10);//Queue degree
        executor.setThreadNamePrefix("customExecutor-1-");//Thread prefix name
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//Configure rejection policy
        executor.setAllowCoreThreadTimeOut(true);// Allow core threads to be destroyed
        executor.initialize();
        return executor;
    }

    @Bean("customExecutor-2")// Custom thread pool 2
    public Executor customExecutor2() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(3);//Core pool size
        executor.setMaxPoolSize(6);//Maximum number of threads
        executor.setKeepAliveSeconds(60);//Thread idle time
        executor.setQueueCapacity(10);//Queue degree
        executor.setThreadNamePrefix("customExecutor-2-");//Thread prefix name
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//Configure rejection policy
        executor.setAllowCoreThreadTimeOut(true);// Allow core threads to be destroyed
        executor.initialize();
        return executor;
    }
}
@Async("customExecutor-1")
public void method1(){}

@Async("customExecutor-2")
public void method2(){}

@Async exception handling

When the method returns a value with Future, Future The get () method will throw an exception, so exception capture is no problem. However, when the method does not have a return value, the main thread cannot catch the exception at this time. Additional configuration is required to handle the exception. There are the following two ways.

1. Handling exceptions through try catch

Use try catch directly in asynchronous methods to handle thrown exceptions. This method can also be used for asynchronous methods with Future return values.

2. By implementing the AsyncUncaughtExceptionHandler interface

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        // Omit the code of custom thread pool
    }

    // Custom exception handling
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
                System.out.println(method.getName() + "Exception occurred! Cause of abnormality:" + throwable.getMessage() );
            }
        };
    }
}

reference material

Topics: Spring Multithreading thread pool