Everyone said that @ Async annotation is not recommended directly? Why?

Posted by antonello on Sun, 26 Dec 2021 13:25:22 +0100

Source: www.cnblogs.com com/wlandwl/p/async. html

This article describes the application of @ Async annotation in Spring system.

This article only describes the application rules of @ Async annotation, not the principle, call logic and source code analysis. For asynchronous method calls, the @ Async annotation has been provided since spring 3, which can be marked on the method to call the method asynchronously. The caller will return immediately upon the call, and the actual execution of the method will be submitted to the task of the Spring TaskExecutor and executed by the thread in the specified thread pool.

In the project application, @ Async calls the thread pool. It is recommended to use the custom thread pool mode.

Common scheme of custom thread pool: re implement the interface AsyncConfigurer.

brief introduction

Application scenario

Synchronization: synchronization means that the whole process is executed in sequence. When each process is completed, the result is returned.

Asynchrony: asynchrony calls are instructions that are only sent. The caller does not need to wait for the called method to be fully executed; Instead, continue with the following process.

For example, in a call, you need to call three process methods a, B and C in sequence; If they are all synchronous calls, they need to be executed in sequence before the process is completed; If B is an asynchronous calling method, after calling A after executing the B, it does not wait for B to complete. Instead, the execution starts calling C. After C is executed, it means that the process is finished.

In Java, when dealing with similar scenarios, it is generally based on creating an independent thread to complete the corresponding asynchronous call logic, through the execution process between the main thread and different business sub threads, so that after the independent thread is started, the main thread continues to execute without stagnation.

Thread pool implemented by Spring

  1. 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.
  2. SyncTaskExecutor: this class does not implement asynchronous call, but only a synchronous operation. Only applicable where multithreading is not required.
  3. ConcurrentTaskExecutor: the adapter class of Executor, which is not recommended. Consider using this class only if the ThreadPoolTaskExecutor does not meet the requirements.
  4. SimpleThreadPoolTaskExecutor: is a class of quartz's SimpleThreadPool. This class is required only if the thread pool is used by both quartz and non quartz.
  5. ThreadPoolTaskExecutor: most commonly used, recommended. Its essence is to Java util. concurrent. Packaging of ThreadPoolExecutor.

Asynchronous methods include:

  1. The simplest asynchronous call, the return value is void
  2. For asynchronous calls with parameters, asynchronous methods can pass in parameters
  3. If there is a return value, it is often called to return Future

Enable @ Async in Spring

// Enabling method based on Java configuration:
@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }

// Spring boot enable:
@EnableAsync
@EnableTransactionManagement
public class SettlementApplication {
    public static void main(String[] args) {
        SpringApplication.run(SettlementApplication.class, args);
    }
}

@Async application default thread pool

Spring applies the default thread pool, which means that the name of the thread pool is not specified when the @ Async annotation is used. Check the source code. The default thread pool of @ Async is SimpleAsyncTaskExecutor.

I won't introduce the basics of Spring Boot. I recommend this practical tutorial:
https://github.com/javastacks...

No return value call

Based on @ Async no return value call, directly annotate the using class and method (it is recommended to use the method). If you need to throw an exception, you need to manually throw a new exception.

/**
 * Asynchronous calls with parameters can pass in parameters
 *  If the return value is void, the exception will be handled by AsyncUncaughtExceptionHandler
 * @param s
 */
@Async
public void asyncInvokeWithException(String s) {
    log.info("asyncInvokeWithParameter, parementer={}", s);
    throw new IllegalArgumentException(s);
}

Future call with return value

/**
 * The exception call returns Future
 *  If the return value is Future, it will not be handled by AsyncUncaughtExceptionHandler. We need to catch and handle exceptions in the method
 *  Or when the caller calls futrue Catch exceptions for processing when getting
 *
 * @param i
 * @return
 */
@Async
public Future<String> asyncInvokeReturnFuture(int i) {
    log.info("asyncInvokeReturnFuture, parementer={}", i);
    Future<String> future;
    try {
        Thread.sleep(1000 * 1);
        future = new AsyncResult<String>("success:" + i);
        throw new IllegalArgumentException("a");
    } catch (InterruptedException e) {
        future = new AsyncResult<String>("error");
    } catch(IllegalArgumentException e){
        future = new AsyncResult<String>("error-IllegalArgumentException");
    }
    return future;
}

Completable future call with return value

Completable future does not use @ Async annotation and can call the system thread pool to process business.

JDK5 adds a Future interface to describe the results of an asynchronous calculation. Although Future and related usage methods provide the ability to execute tasks asynchronously, it is very inconvenient to obtain the results. The results of tasks can only be obtained by blocking or polling. The blocking method is obviously contrary to the original intention of our asynchronous programming. The polling method will consume unnecessary CPU resources and can not get the calculation results in time.

  • CompletionStage represents a stage in the asynchronous computing process. After one stage is completed, another stage may be triggered
  • The calculation execution of a stage can be a Function, Consumer or Runnable. For example: stage thenApply(x -> square(x)). thenAccept(x -> System.out.print(x)). thenRun(() -> System. out. println())
  • The execution of a stage may be triggered by the completion of a single stage or by multiple stages together

In Java 8, completable Future provides a very powerful extension function of Future, which can help us simplify the complexity of asynchronous programming, provide the ability of functional programming, process calculation results through callback, and provide methods for transforming and combining completable Future.

  • It may represent an explicitly completed Future or a completion stage. It supports triggering some functions or executing some actions after the calculation is completed.
  • It implements the Future and CompletionStage interfaces
/**
 * Data query thread pool
 */
private static final ThreadPoolExecutor SELECT_POOL_EXECUTOR = new ThreadPoolExecutor(10, 20, 5000,
        TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), new ThreadFactoryBuilder().setNameFormat("selectThreadPoolExecutor-%d").build());

// tradeMapper. The counttradelog (tradesearchbean) method indicates the quantity obtained, and the return value is int
// Get the total number of entries
    CompletableFuture<Integer> countFuture = CompletableFuture
            .supplyAsync(() -> tradeMapper.countTradeLog(tradeSearchBean), SELECT_POOL_EXECUTOR);
// Synchronous blocking
CompletableFuture.allOf(countFuture).join();
// Get results
int count = countFuture.get();

Disadvantages of default thread pool

In the application of thread pool, refer to Alibaba java development specification: thread pool is not allowed to be created by Executors, and the default thread pool of the system is not allowed. It is recommended to use ThreadPoolExecutor. This processing method allows development engineers to more clearly understand the operation rules of thread pool and avoid the risk of resource depletion. Disadvantages of Executors' methods:

  • newFixedThreadPool and newsinglethreadexecution: the main problem is that the stacked request processing queue may consume a lot of memory, even OOM.
  • newCachedThreadPool and newScheduledThreadPool: the problem is that the maximum number of threads is integer MAX_ Value, a very large number of threads may be created, even OOM.

@The default asynchronous configuration of Async uses SimpleAsyncTaskExecutor. The thread pool creates one thread per task by default. If the system continues to create threads, the system will eventually occupy too much memory and cause OutOfMemoryError error.

For thread creation, SimpleAsyncTaskExecutor provides a current limiting mechanism. The switch is controlled through the concurrencyLimit attribute. When concurrencyLimit > = 0, the current limiting mechanism is turned on. By default, the current limiting mechanism is turned off, that is, concurrencyLimit=-1. When it is turned off, new threads will be created to process tasks. Based on the default configuration, SimpleAsyncTaskExecutor is not a thread pool in the strict sense and cannot achieve the function of thread reuse.

@Async application custom thread pool

The user-defined thread pool can control the thread pool more finely, which is convenient to adjust the size configuration of the thread pool, and the thread performs exception control and handling. When setting the system custom thread pool to replace the default thread pool, although it can be set through multiple modes, there is and can only be one thread pool generated by replacing the default thread pool (multiple classes cannot be set to inherit asyncconfigurator). The custom thread pool has the following modes:

  • Re implement the interface AsyncConfigurer
  • Inherit AsyncConfigurerSupport
  • Configure a custom TaskExecutor to replace the built-in task executor

By viewing the Spring source code's default calling rules for @ Async, you will first query the classes that implement the AsyncConfigurer interface in the source code. The class that implements this interface is AsyncConfigurerSupport. However, the default configured thread pool and asynchronous processing method are empty, so you need to specify a thread pool whether you inherit or re implement the interface. And re implement the public executor getasyncexecution() method.

Implementation interface AsyncConfigurer

@Configuration
public class AsyncConfiguration implements AsyncConfigurer {
   @Bean("kingAsyncExecutor")
   public ThreadPoolTaskExecutor executor() {
       ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
       int corePoolSize = 10;
       executor.setCorePoolSize(corePoolSize);
       int maxPoolSize = 50;
       executor.setMaxPoolSize(maxPoolSize);
       int queueCapacity = 10;
       executor.setQueueCapacity(queueCapacity);
       executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
       String threadNamePrefix = "kingDeeAsyncExecutor-";
       executor.setThreadNamePrefix(threadNamePrefix);
       executor.setWaitForTasksToCompleteOnShutdown(true);
       // Use the custom cross thread request level thread factory class 19 int awaitterminationseconds = 5;
       executor.setAwaitTerminationSeconds(awaitTerminationSeconds);
       executor.initialize();
       return executor;
   }

   @Override
   public Executor getAsyncExecutor() {
       return executor();
   }

   @Override
   public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
       return (ex, method, params) -> ErrorLogger.getInstance().log(String.format("Perform asynchronous tasks'%s'", method), ex);
   }
}

Inherit AsyncConfigurerSupport

@Configuration
@EnableAsync
class SpringAsyncConfigurer extends AsyncConfigurerSupport {

    @Bean
    public ThreadPoolTaskExecutor asyncExecutor() {
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        threadPool.setCorePoolSize(3);
        threadPool.setMaxPoolSize(3);
        threadPool.setWaitForTasksToCompleteOnShutdown(true);
        threadPool.setAwaitTerminationSeconds(60 * 15);
        return threadPool;
    }

    @Override
    public Executor getAsyncExecutor() {
        return asyncExecutor;
}

  @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    return (ex, method, params) -> ErrorLogger.getInstance().log(String.format("Perform asynchronous tasks'%s'", method), ex);
}
}

Configure custom TaskExecutor

Since the default thread pool of AsyncConfigurer is empty in the source code, Spring uses beanfactory GetBean (TaskExecutor. Class) first checks whether there is a wired process pool. If it is not configured, use beanfactory GetBean (default_task_executor_bean_name, executor. Class), and query whether there is a thread pool with the default name of TaskExecutor.

Therefore, a bean named TaskExecutor can be defined in the project to generate a default thread pool. You can also declare a thread pool without specifying the name of the thread pool, which is based on TaskExecutor Class.

For example:

Executor.class:ThreadPoolExecutorAdapter->ThreadPoolExecutor->AbstractExecutorService->ExecutorService->Executor

In this mode, the final bottom layer is executor Class. When replacing the default thread pool, you need to set the default thread pool name to TaskExecutor

TaskExecutor.class:ThreadPoolTaskExecutor->SchedulingTaskExecutor->AsyncTaskExecutor->TaskExecutor

In this mode, the bottom layer is taskexecutor Class, when replacing the default thread pool, the thread pool name may not be specified.

@EnableAsync
@Configuration
public class TaskPoolConfig {
   @Bean(name = AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME)
   public Executor taskExecutor() {
       ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //Core thread pool size
       executor.setCorePoolSize(10);
       //Maximum number of threads
       executor.setMaxPoolSize(20);
       //Queue capacity
       executor.setQueueCapacity(200);
       //Active time
       executor.setKeepAliveSeconds(60);
       //Thread name prefix
       executor.setThreadNamePrefix("taskExecutor-");
       executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
       return executor;
   }
  @Bean(name = "new_task")
   public Executor taskExecutor() {
       ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //Core thread pool size
       executor.setCorePoolSize(10);
       //Maximum number of threads
       executor.setMaxPoolSize(20);
       //Queue capacity
       executor.setQueueCapacity(200);
       //Active time
       executor.setKeepAliveSeconds(60);
       //Thread name prefix
       executor.setThreadNamePrefix("taskExecutor-");
       executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
       return executor;
   }
}

Multiple thread pools

@Async annotation, which uses the system default or custom thread pool (instead of the default thread pool). Multiple thread pools can be set in the project. When calling asynchronously, indicate the name of the thread pool to be called, such as @ Async("new_task").

@Async part of the important source code analysis

Source code - get thread pool method

Source code - set the default thread pool defaultExecutor, which is empty by default. When you re implement getAsyncExecutor() of the interface AsyncConfigurer, you can set the default thread pool.

Source code - when the default thread pool set in the project is not found, the spring default thread pool is used

Recent hot article recommendations:

1.1000 + Java interview questions and answers (2021 latest version)

2.Hot! The Java collaboration is coming...

3.Play big! Log4j 2.x re explosion...

4.Spring Boot 2.6 was officially released, a wave of new features..

5.Java development manual (Songshan version) is the latest release. Download it quickly!

Feel good, don't forget to like + forward!

Topics: Java