Spring asynchronous programming

Introduction

In Spring, annotated methods with @Async annotations become asynchronous methods. It will be executed in a separate thread outside the current thread of the caller, which is equivalent to that we directly execute a new thread:

new Thread(() -> System.out.println("new: " + Thread.currentThread().getName())).start();

Why use asynchronous execution?

Many times we need to call a time-consuming or non-main link method. Its execution result is not important. We don't need to block and wait for its execution to end before executing subsequent methods. The synchronization method is that the tasks are executed sequentially, which is less efficient. The total time of the task is the sum of the time consumed by each subtask.

The advantage of using asynchronous execution is to improve the execution efficiency of the task. If you do not care about the execution time of the subtasks, the total time of the task is the execution time of the main task.

use

@Async

Focus on key points:

  1. Point out that the annotation can also mark the class, and all methods of the representative class are asynchronous methods
  2. Any parameter type is supported, but the method return value must be void or Future type. When using Future, you can use ListenableFuture interface or CompletableFuture class that implements Future interface to interact better with asynchronous tasks. If the asynchronous method has a return value and the Future type is not used, the caller cannot get the return value.
  3. @Async can specify a specific BeanName (ie thread pool, Executor/TaskExecutor) to execute the method, and modify all methods that represent the class on the class to be executed with the bean, and the method level modification is higher than the class level modification
  4. The annotation @EnableAsync needs to be configured on the calling asynchronous method class
/**
 * Annotation that marks a method as a candidate for <i>asynchronous</i> execution.
 * Can also be used at the type level, in which case all of the type's methods are
 * considered as asynchronous.
 *
 * <p>In terms of target method signatures, any parameter types are supported.
 * However, the return type is constrained to either {@code void} or
 * {@link java.util.concurrent.Future}. In the latter case, you may declare the
 * more specific {@link org.springframework.util.concurrent.ListenableFuture} or
 * {@link java.util.concurrent.CompletableFuture} types which allow for richer
 * interaction with the asynchronous task and for immediate composition with
 * further processing steps.
 *
 * <p>A {@code Future} handle returned from the proxy will be an actual asynchronous
 * {@code Future} that can be used to track the result of the asynchronous method
 * execution. However, since the target method needs to implement the same signature,
 * it will have to return a temporary {@code Future} handle that just passes a value
 * through: e.g. Spring's {@link AsyncResult}, EJB 3.1's {@link javax.ejb.AsyncResult},
 * or {@link java.util.concurrent.CompletableFuture#completedFuture(Object)}.
 *
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 3.0
 * @see AnnotationAsyncExecutionInterceptor
 * @see AsyncAnnotationAdvisor
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {

    /**
     * A qualifier value for the specified asynchronous operation(s).
     * <p>May be used to determine the target executor to be used when executing this
     * method, matching the qualifier value (or the bean name) of a specific
     * {@link java.util.concurrent.Executor Executor} or
     * {@link org.springframework.core.task.TaskExecutor TaskExecutor}
     * bean definition.
     * <p>When specified on a class level {@code @Async} annotation, indicates that the
     * given executor should be used for all methods within the class. Method level use
     * of {@code Async#value} always overrides any value set at the class level.
     * @since 3.1.2
     */
    String value() default "";

}

Basic use

Define the interface

The test method is decorated with @Async annotation

public interface AsyncTestService {
    @Async
    void test();
}

Implement the interface

The method entry prints the thread name, and the output task ends after sleep

@Service
public class AsyncTestServiceImpl implements AsyncTestService {

    @Override
    public void test() {
        System.out.println("测试异步调用,ThreadName:" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("异步调用调用结束!");
    }
}

Start class

Record the execution time, print the thread name, call the test method asynchronously, the main thread continues to output the result, and the output task ends after sleep

@SpringBootApplication
public class SpringdemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(SpringdemoApplication.class, args);

        long start = System.currentTimeMillis();
        System.out.println("主线程执行,ThreadName:" + Thread.currentThread().getName());
        AsyncTestService testService = (AsyncTestService) applicationContext.getBean("asyncTestServiceImpl");
        testService.test();
        try {
            System.out.println("主线程输出:Hello World!");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程执行完毕,耗时:" + (System.currentTimeMillis() - start));
    }
}

Test Results

The print results are as follows, analysis: the main thread execution output "main thread execution, ThreadName: main"; testService.test() is asynchronous execution, the main thread output "main thread output: Hello World!"; the main thread sleep, the child thread executes asynchronously The test method outputs "Test asynchronous call, ThreadName: task-1"; because the sleep time of the test method is short, it outputs "Asynchronous call end!"; Finally, the main thread wakes up and outputs "Main thread execution completed, time-consuming: 2012" . You can see that the test method is executed asynchronously.

Main thread execution, ThreadName: main

Main thread output: Hello World!

Test asynchronous call, ThreadName:task-1

The end of the asynchronous call!

The main thread is executed, time-consuming: 2012

Synchronous comparison

Comment out the @Async annotation in the interface, which is equivalent to synchronous execution and see the running result

public interface AsyncTestService {
    @Async
    void test();
}

Main thread execution, ThreadName: main

Test asynchronous call, ThreadName: main

The end of the asynchronous call!

Main thread output: Hello World!

The main thread is executed, time-consuming: 7008

It can be seen that the time consumption has increased from 2012 to 7008 (the specific time consumption can be measured several times and the average value is taken, but the trend is the same), which seriously affects the efficiency.

Custom thread pool

Define thread pool bean

@EnableAsync(mode = AdviceMode.PROXY)
@Configuration
public class AsyncThreadPoolConfig {

    /**
     * 核心线程数(默认线程数)
     */
    private static final int CORE_POOL_SIZE = 8;

    /**
     * 最大线程数
     */
    private static final int MAX_POOL_SIZE = 20;

    /**
     * 允许线程空闲时间(单位:默认为秒)
     */
    private static final int KEEP_ALIVE_TIME = 10;

    /**
     * 缓冲队列大小
     */
    private static final int QUEUE_CAPACITY = 200;

    /**
     * 线程池名前缀
     */
    private static final String THREAD_NAME_PREFIX = "async-task-pool-";

    /**
     * 当使用@Async注解时,需指定使用此线程池
     *
     * @return 线程池实例
     */
    @Bean("asyncTaskExecutor")
    public ThreadPoolTaskExecutor asyncTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.setQueueCapacity(QUEUE_CAPACITY);
        executor.setKeepAliveSeconds(KEEP_ALIVE_TIME);
        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);

        executor.setThreadFactory(new ThreadFactory() {
            // 线程计数器
            private final AtomicInteger threadNumber = new AtomicInteger(0);

            @Override
            public Thread newThread(@NotNull Runnable runnable) {
                Thread thread = new Thread(runnable, THREAD_NAME_PREFIX + threadNumber.getAndIncrement());
                if (thread.isDaemon()) {
                    thread.setDaemon(false);
                }
                if (thread.getPriority() != Thread.NORM_PRIORITY) {
                    thread.setPriority(Thread.NORM_PRIORITY);
                }
                return thread;
            }
        });
        // 线程池对拒绝任务的处理策略,拒绝执行且抛出异常
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

Define the interface

Specify the value of @Aysnc as the beanName of the thread pool

public interface AsyncTestService {
    @Async(value = "asyncTaskExecutor")
    void test();
}

Test Results

The implementation is tired, the startup is the same as the above, the test results are as follows, you can see that the asynchronous thread name has been changed to the configured rule

Main thread execution, ThreadName: main

Main thread output: Hello World!

Test asynchronous call, ThreadName: async-task-pool-0

The main thread is executed, time-consuming: 2022

The end of the asynchronous call!

Compared

In the context of a custom thread pool, we can do many configurations compared to the basic use. Common configurations are as follows:

  1. Custom core threads
  • CPU intensive: N + 1
  • IO-intensive: 2 * N + 1
  1. Custom thread pool buffer queue size
  • If the task processing is more frequent in this scenario, the buffer size needs to be enlarged, and vice versa.
  1. Choosing the right saturation strategy
  • AbortPolicy (default policy): Throw RejectedExecutionException to reject the processing of new tasks

  • DiscardOldestPolicy: discard the oldest unprocessed task request

  • DiscardPolicy: Discard the task directly without executing it

  • CallerRunsPolicy: Executing this command in the thread that calls execute will block the entrance

Thread pool execution strategy

  1. When the task comes, if the current number of worker threads is less than the number of core threads corePoolSize, a new thread is created to perform the task
  2. If the current number of worker threads is equal to or greater than the number of core threads, the task will be added to the blocking queue BlockingQueue
  3. If the task cannot be added to the blocking queue (the queue is full), create a non-corePool thread to process the task
  4. If a non-corePool thread is created so that the number of thread pool threads is greater than the maximum number of threads maxmumPoolSize, the RejectedExecutionHandler.rejectedExecution() of the saturation strategy is executed

Guess you like

Origin blog.csdn.net/Dkangel/article/details/111933110