Async usage details

Spring Boot calls @Async asynchronously

In actual development, sometimes in order to process requests and respond in a timely manner, we may execute multiple tasks at the same time, or process the main task first, that is, asynchronous calls. There are many implementations of asynchronous calls, such as multi-threading, timing tasks, and message queues. wait,

1. Ordinary serial execution demonstration

1.1 Task class

Assuming that there are three tasks that need to be processed, we will write code in a logical order in our usual development;

@Component
public class Task {
    
    

    public static Random random =new Random();

    public void doTaskOne() throws Exception {
    
    
        System.out.println("开始做任务一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
    }

    public void doTaskTwo() throws Exception {
    
    
        System.out.println("开始做任务二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(5000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
    }

    public void doTaskThree() throws Exception {
    
    
        System.out.println("开始做任务三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
    }
}

1.2 Test class

@SpringBootTest
class ApplicationTests {
    
    

    @Test
    void contextLoads() {
    
    
    }
    @Autowired
    private Task task;

    @Test
    public void test() throws Exception {
    
    
        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();
    }
}

1.3 Running results and analysis

The observation results show that task 1 is called first, task 2 can only be started after task 1 is completed, and task 3 can only be executed after task 2 is completed; this kind of execution program of the serial language does not have any problems with subsequent tasks in actual development.
insert image description here
If there is such a function, the second task is to send emails, and the third task is to send text messages. In actual development, these two tasks complement each other and interfere with each other; it doesn’t matter if it’s a serial operation, isn’t it a bit inefficient! The best way to make these two tasks operate in parallel
Asynchronous way:

  • 1. Using multi-threading, assign Task 1 and Task 2 respectively in the main thread to execute tasks
  • 2. Use the @Async annotation to achieve asynchrony

Two, @Async use demonstration

2.1 Add @EnableAsync to the startup class to enable asynchrony

@AsyncYes Spring内置注解, it is used to handle asynchronous tasks, SpringBootand it is also applicable in , and in SpringBootthe project, except for the starter of boot itself, no additional dependencies are required.
To use it @Async, you need to add an active declaration to the startup class @EnableAsyncto start the asynchronous method.

@SpringBootApplication
@EnableAsync
public class Application {
    
    

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

}

2.2 Write the task class, add @Async to the task method

@Component
public class TaskAsync {
    
    
    public static Random random = new Random();

    @Async
    public void doTaskOne() throws Exception {
    
    
        System.out.println("开始做任务一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
    }

    @Async
    public void doTaskTwo() throws Exception {
    
    
        System.out.println("开始做任务二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
    }

    @Async
    public void doTaskThree() throws Exception {
    
    
        System.out.println("开始做任务三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
    }
}

2.3 Test class

@SpringBootTest
class ApplicationTests {
    
    

    @Test
    void contextLoads() {
    
    
    }
    @Autowired
    private Task task;
    @Autowired
    private TaskAsync taskAsync;

    @Test
    public void test() throws Exception {
    
    
        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();
    }

    @Test
    public void taskAsynctest() throws Exception {
    
    
        try {
    
    
            long start = System.currentTimeMillis();
            taskAsync.doTaskOne();
            taskAsync.doTaskTwo();
            taskAsync.doTaskThree();
            Thread.sleep(10000);
            long end = System.currentTimeMillis();
            System.out.println("end = " + (end - start)/1000f);

        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        
    }
}

2.3 Observation results of multiple runs

Then we are running the test class. At this time, the output may be varied. Any task may be executed first, and some methods may have no output because the main program is closed.
insert image description here

2.4 Summary

Using @Async can realize the asynchronous execution of the program and complete the program optimization; but in actual use, it is necessary to pay attention to the problem of asynchronous failure: the
called method and the calling method are processed in the same class, which will cause asynchronous failure

  • in the same class. invalid code
class TestService {
    
    
    void a() {
    
     
      this.b();
    }
	
    @Async
    void b(){
    
    }
}
  • normal code
class TestService {
    
    
    void a(){
    
     
       BService.b();
    }
}

class BService() {
    
    
    @Async
    void b(){
    
    }
}

Find the bug of the Spring framework from the @Async case: a large analysis of the reasons why exposeProxy=true does not take effect + the best solution
@Async failure

3. Use of @Async + thread pool

@Async asynchronous method uses Spring to create ThreadPoolTaskExecutor by default (refer to TaskExecutionAutoConfiguration), where the default number of core threads is 8, the default maximum queue and the default maximum number of threads are both Integer.MAX_VALUE. The condition for creating a new thread is when the queue is full, and such a configuration queue will never be full. If there are methods marked with @Async annotations that occupy threads for a long time (such as HTTP long connections waiting to get results), the core 8 threads occupy After it is full, new calls will enter the queue, and the external performance is not executed.

We can customize a thread pool. The setting of the number of threads needs to consider whether the tasks to be performed are IO-intensive tasks or CPU-intensive tasks. For CPU-intensive tasks, such as the number of CPU cores + 1; for IO-intensive tasks, since the IO-intensive task threads are not always executing tasks, you should configure as many threads as possible, such as the number of CPU cores * 2.

3.1 Thread pool configuration class

Next, a thread pool configuration code for an IO-intensive task is given


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class ThreadPoolConfig {
    
    

    /**
     * 核心线程数
     */
    private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2;

    /**
     * 最大线程数
     */
    private static final int MAX_POOL_SIZE = CORE_POOL_SIZE * 4 < 256 ? 256 : CORE_POOL_SIZE * 4;

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

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

    /**
     * 线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁
     */
    private static final int AWAIT_TERMINATION = 60;

    /**
     * 用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
     */
    private static final Boolean WAIT_FOR_TASKS_TO_COMPLETE_ON_SHUTDOWN = true;

    /**
     * 线程池名前缀
     */
    private static final String THREAD_NAME_PREFIX = "Spider-ThreadPool-";


    @Bean("spiderTaskExecutor")
    public ThreadPoolTaskExecutor spiderTaskExecutor () {
    
    
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        taskExecutor.setKeepAliveSeconds(KEEP_ALIVE_TIME);
        taskExecutor.setQueueCapacity(QUEUE_CAPACITY);
        taskExecutor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        taskExecutor.setWaitForTasksToCompleteOnShutdown(WAIT_FOR_TASKS_TO_COMPLETE_ON_SHUTDOWN);
        taskExecutor.setAwaitTerminationSeconds(AWAIT_TERMINATION);
        /**
         * 拒绝策略 => 当pool已经达到max size的时候,如何处理新任务
         * CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
         * AbortPolicy:直接抛出异常,这是默认策略;
         * DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
         * DiscardPolicy:直接丢弃任务;
         */
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }

}

3.2 Modify task class

On the method in the task class add@Async("spiderTaskExecutor")

@Component
public class TaskAsync {
    
    
    public static Random random = new Random();

    @Async("spiderTaskExecutor")
    public void doTaskOne() throws Exception {
    
    
        System.out.println("开始做任务一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
    }

    @Async("spiderTaskExecutor")
    public void doTaskTwo() throws Exception {
    
    
        System.out.println("开始做任务二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
    }

    @Async("spiderTaskExecutor")
    public void doTaskThree() throws Exception {
    
    
        System.out.println("开始做任务三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
    }
}

Finally, some knowledge should be added, and the number of threads should be reasonably controlled (for example, order details and article information should be collected at the same time as order information is collected, and order details and article information can be processed in one thread), and do not abuse it. It is necessary to consider when to use MQ and when to start thread asynchronous processing. Recommend a tool for analyzing jstack files, IBM Thread and Monitor Dump Analyzer for Java, to analyze running, deadlocked, waiting, and blocked threads.

Note: @async failure reason

Four get asynchronous execution results

The above demonstrates @Async, but sometimes in addition to the concurrent scheduling of tasks, we also need to obtain the return value of the task, and end the main task after the execution of multiple tasks is completed. How should we deal with it at this time?
Detailed use of CompletableFuture

Five source address

source carousel

Six extended thinking

Have you ever wondered how we can guarantee the correctness of the call if the asynchronous call fails?
In actual work, heavy processing is a very common scenario, such as:

  • Failed to send message
  • Failed to call remote service.
  • Failed to contend for the lock.

These errors may be caused by network fluctuations, and reprocessing will succeed after waiting. Generally speaking, grammars such as try/catch and while loops are used for reprocessing, but this approach lacks uniformity and is not very convenient, requiring a lot of code to be written. However, spring-retry can elegantly implement the reprocessing function through annotations without invading the original business logic code.

@Retryable (spring's retry mechanism)

Guess you like

Origin blog.csdn.net/weixin_43811057/article/details/131002678