SpringBoot (thirteen) asynchronous tasks

Table of contents

asynchronous task

1.1 What is asynchronous

1. Java thread processing

2. SpringBoot asynchronous task

2.1 Use the annotation @EnableAsync to enable asynchronous task support

2.2. Use the @Async annotation to mark the method to be executed asynchronously

2.3, controller test

3. Restrictions related to asynchronous tasks

4. Custom Executor (custom thread pool)

4.1. Application level:

4.2. Method level:


asynchronous task

Sometimes, the front-end may submit a time-consuming task. If the back-end executes the time-consuming task directly after receiving the request, the front-end will have to wait for a long time before receiving the response. If the time-consuming task is directly requested through the browser, the browser page will always be in a waiting state in circles.

In fact, when the backend wants to process a time-consuming task, it usually submits the time-consuming task to an asynchronous task for execution. At this time, after the front-end submits the time-consuming task, it can return directly to perform other operations.

1.1 What is asynchronous

Asynchronous: If there is a dormant task in the method, the next task is executed directly without waiting for the task to be executed

Simply put: the client sends a request, you can skip the method and execute the next method,

If one of the A methods has a sleep task, there is no need to wait, and the next method is executed directly. The asynchronous task (A method) will be executed in the background, and the A method will be executed after the sleep time of the A method is up.

Synchronization: You must wait for the task to be executed and get the result before executing the next task. .

And 异步for example:

setTimeout(function cbFn(){
    console.log('learnInPro');
}, 1000);
console.log('sync things');

setTimeout is one 异步任务. When the JS engine executes setTimeout sequentially and finds that it is an asynchronous task, it will suspend the task and continue to execute the following code. The callback function cbFn will not be executed until after 1000ms, which is asynchronous. When the execution reaches setTimeout, JS will not wait stupidly for 1000ms to execute the cbFn callback function, but continue to execute the following code

Concept : The so-called "asynchronous" simply means that a task is not completed continuously . It can be understood that the task is artificially divided into two sections, the first section is executed first, and then other tasks are executed .

Correspondingly, continuous execution is called synchronization. Since it is executed continuously, other tasks cannot be inserted, so the program can only wait while the operating system reads files from the hard disk.

1. Java thread processing

In Java, the most common way to start an asynchronous task is to create a thread to execute the asynchronous task, as shown below:

@RestController
@RequestMapping("async")
public class AsyncController {
@GetMapping("/")
public String index() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                // 模拟耗时操作
                Thread.sleep(TimeUnit.SECONDS.toMillis(5));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
    return "consuming time behavior processing!";
}
}

At this time, the browser requests localhost:8080/async/, and the response can be quickly obtained, and the time-consuming task will be executed in the background.

Generally speaking, the front-end does not pay attention to the results of time-consuming tasks, so the front-end only needs to be responsible for submitting the task to the back-end. However, if the front end needs to obtain the results of time-consuming tasks, the results can be returned through Future, etc. The details are as follows

public class MyReturnableTask implements Callable<String> {
    @Override
    public String call() throws Exception {
        long startTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+"线程运行开始");
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName()+"线程运行结束");
        return "result";
    }
}
@GetMapping("/task")
    public void task() throws ExecutionException, InterruptedException {
        MyReturnableTask myReturnableTask = new MyReturnableTask();
        FutureTask<String> futureTask = new FutureTask<String>(myReturnableTask);
        Thread thread = new Thread(futureTask, "returnableThread");
        thread.start();
        String s = futureTask.get();
        System.out.println(s);
    }

In fact, in Spring Boot, we don't need to manually create threads to execute time-consuming tasks asynchronously, because the Spring framework has provided related asynchronous task execution solutions. This article mainly introduces the relevant content of executing asynchronous tasks in Spring Boot.

2. SpringBoot asynchronous task

2.1 Use the annotation @EnableAsync to enable asynchronous task support

@SpringBootApplication
@EnableAsync//开启异步任务支持
public class ApplicationStarter {
  public static void main(String[] args) {
      SpringApplication.run(ApplicationStarter.class,args);
  }
}

2.2. Use @Asyncannotations to mark methods for asynchronous execution

public interface AsyncService {
    //异步任务
    void t1();
​
    Future<String> t2();
}
@Service
public class AsyncServiceImpl implements AsyncService {
​
    //使用@Async注解标记的方法 会提交到一个异步任务中进行执行,第一次不会执行该方法,
    //如果不添加该注解,controller中调用该方法会等待5秒在响应
    //异步任务
    @Async
    public void t1() {
        // 模拟耗时任务
        try {
            Thread.sleep(TimeUnit.SECONDS.toMillis(5));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //因为该异步方法中使用了休眠,所以过5秒才会执行下面代码
        System.out.println("异步方法中:耗时时间已走完");
    }
​
    @Async
    public Future<String> t2(){
        // 模拟耗时任务
        try {
            Thread.sleep(TimeUnit.SECONDS.toMillis(5));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new AsyncResult<>("async tasks done!");
    }
​
​
}

2.3, controller test

@RestController
public class AsyncController {
    @Autowired
    private AsyncService asyncService;
​
    @GetMapping("/task1")
    public String asyncTaskWithoutReturnType(){
     //因为下面方法是异步任务方法,前端请求过来,会先跳过异步方法(异步任务会在后台得到执行),接着会执行下面代码。
    //因为异步任务中有耗时任务,在因为异步任务会在后台得到执行,所以等待时间耗完,就会在执行异步方法中的内容(这相当于回调了)
      asyncService.t1();
       return "rrrr";
    }
​
    @GetMapping("/task2")
    public String asyncTaskWithReturnType() throws InterruptedException, ExecutionException {
        asyncService.t2();
        return "aaa";
    }
}

The method annotated by @Async can accept any type of parameter, but can only return void or Future type data

Therefore, when the asynchronous method returns data, it is necessary to use Future to wrap the asynchronous task result. The above code uses AsyncResult to wrap the asynchronous task result. AsyncResult indirectly inherits Future, which is a wrapper class provided by Spring that can be used to track the execution result of the asynchronous method. Other commonly used Future types include ListenableFuture provided by Spring 4.2, or CompletableFuture provided by JDK 8, which can provide richer asynchronous task operations.

If the front end needs to obtain the time-consuming task results, the asynchronous task method should return a Future type data. At this time, the Controller related interface needs to call the get() method of the Future to obtain the asynchronous task results. The get() method is a blocking method, so the The operation is equivalent to converting an asynchronous task into a synchronous task. The browser will also face the waiting process we mentioned earlier, but asynchronous execution still has its advantages, because we can control the calling sequence of the get() method, so we can first After performing some other operations, finally call the get() method

3. Restrictions related to asynchronous tasks

The asynchronous task method annotated by @Async has related restrictions:

The method annotated by @Async must be public so that the method can be proxied.

The @Async method cannot be called in the same class, because the call in the same class will bypass the method proxy and call the actual method.

Methods annotated with @Async cannot be static.

The @Async annotation cannot be annotated into the same method with the lifecycle callback function of the Bean object (such as @PostConstruct). The solution can refer to: Spring - The @Async annotation

Asynchronous classes must be injected into the Spring IOC container (that is, asynchronous classes must be annotated by @Component/@Service, etc.).

Asynchronous class objects used in other classes must be injected through methods such as @Autowired, and new objects cannot be manually created.

4. Custom Executor (custom thread pool)

By default, Spring will automatically search for the relevant thread pool definition: either a unique TaskExecutor Bean instance, or an Executor Bean instance named taskExecutor. If none of these two Bean instances exist, SimpleAsyncTaskExecutor will be used to asynchronously execute the method annotated by @Async.

To sum up, we can know that, by default, the Executor used by Spring is SimpleAsyncTaskExecutor. SimpleAsyncTaskExecutor will create a new thread every time it is called, and will not reuse the previous thread. Many times, this implementation method does not meet our business scenarios, so we usually customize an Executor to replace SimpleAsyncTaskExecutor.

For custom Executor (custom thread pool), it can be divided into the following two levels:

Application level: Executor that takes effect globally. According to Spring's default search mechanism, it is actually just to configure a globally unique TaskExecutor instance or an Executor instance named taskExecutor, as follows:

Method level: specify the running thread pool for one or more methods, and other unspecified asynchronous methods run in the default thread pool. As follows:

4.1. Application level:

The following code defines an Executor named taskExecutor, and the @Async method will run in this Executor by default.

@Configuration
public class ExcuterConfig {
    @Bean("taskExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        int cores = Runtime.getRuntime().availableProcessors();
        executor.setCorePoolSize(cores);
        // 设置最大线程数
        executor.setMaxPoolSize(20);
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 设置线程默认前缀名
        executor.setThreadNamePrefix("Application-Level-Async-");
        return executor;
    }
}

4.2. Method level:

package com.buba.config;
import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.task.TaskExecutor;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    /**
    
     * @author qlx
       */
       @Configuration
       public class ExcuterConfig {
       @Bean("methodLevelExecutor1")
       public TaskExecutor getAsyncExecutor1() {
           ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
       // 设置核心线程数
           executor.setCorePoolSize(4);
           // 设置最大线程数
           executor.setMaxPoolSize(20);
           // 等待所有任务结束后再关闭线程池
           executor.setWaitForTasksToCompleteOnShutdown(true);
           // 设置线程默认前缀名
           executor.setThreadNamePrefix("Method-Level-Async1-");
           return executor;
       }
    
       @Bean("methodLevelExecutor2")
       public TaskExecutor getAsyncExecutor2() {
           ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
       // 设置核心线程数
       executor.setCorePoolSize(8);
        // 设置最大线程数
        executor.setMaxPoolSize(20);
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 设置线程默认前缀名
        executor.setThreadNamePrefix("Method-Level-Async2-");
        return executor;
    }
    }

Multiple TaskExecutors are deliberately set above, because if only one TaskExecutor is set, then Spring will use the TaskExecutor as the Executor of all @Async by default, and if multiple TaskExecutors are set, Spring detects that there are multiple Executors globally, and it will be downgraded. The default SimpleAsyncTaskExecutor, at this point we can configure the execution thread pool for the @Async method, and other unconfigured @Async will run in the SimpleAsyncTaskExecutor by default, which is the custom Executor at the method level. As shown in the following code:

@Service
public class AsyncService {
    @Async("methodLevelExecutor1")
    public void t1() throws InterruptedException {
        // 模拟耗时任务
        Thread.sleep(TimeUnit.SECONDS.toMillis(5));
    }
    @Async("methodLevelExecutor2")
    public Future<String> t2() throws InterruptedException {
        // 模拟耗时任务
        Thread.sleep(TimeUnit.SECONDS.toMillis(5));
        return new AsyncResult<>("async tasks done!");
    }
}

Guess you like

Origin blog.csdn.net/m0_65992672/article/details/130422166