Use of asynchronous call Async

Basic case, synchronous call and execution

1. Create a new springboot project

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.sync</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.sync.demo.DemoApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

2. Code structure

3. Imitate business code

@RestController
public class OnBoradingController {
    @Autowired
    private OnBoardingService service;
    @RequestMapping("/b")
    public boolean onBoarding(){
        System.out.printf("controller thread %s-%s handle\n",Thread.currentThread().getId(),Thread.currentThread().getName());
        return service.onBoard();
    }
}
@Service
public class OnBoardingService {
    @Autowired
    private DownService service;

    public boolean onBoard(){
        System.out.printf("Successful account opening\n");
        System.out.printf("Enter the next stage\n");
        service.downloadDoc();
        System.out.printf("I finished it first\n");
        return true;
    }
}
@Service
public class DownService {
    
    void downloadDoc() {
        System.out.printf("doc thread %s-%s handle\n",Thread.currentThread().getId(),Thread.currentThread().getName());
        System.out.printf("Start processing document%tT%n",new Date());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("Processing document complete%tT%n",new Date());
    }
    
    Future<String> Verification(){
        System.out.printf("doc thread %s-%s handle\n",Thread.currentThread().getId(),Thread.currentThread().getName());
        System.out.printf("Start validation request%tT%n",new Date());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("Verification passed%tT%n",new Date());
        return new AsyncResult<>("636-ghd13-13sa");
    }
}

4. Startup class

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

Modify to execute downloaddoc asynchronously (no return value)

5. Add asynchronous annotations to the startup class

@SpringBootApplication
@EnableAsync
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

6. Add asynchronous annotation to the method

@Async
void downloadDoc() {
    ...
}
  • Asynchronous method cannot be defined as static type
  • Calling method and asynchronous method cannot be defined in the same class

At this time, a total of two different thread IDs and names are output, which are asynchronous call execution

Next, perform verification asynchronously (with return value)

7. Add asynchronous annotation to the method

@Async
Future<String> Verification(){
    ...
}

8. Call

public boolean onBoard(){
    System.out.printf("Successful account opening\n");
    Future<String> verification = service.Verification();
    //Since the result is needed, I have to wait for the result, which is similar to that of synchronization
    while (!verification.isDone()){}
    try {
        System.out.printf("Verification successful id--%s\n",verification.get());
    } catch (Exception e) {
    }
    System.out.printf("Enter the next stage\n");
    service.downloadDoc();
    System.out.printf("I finished it first\n");
    return true;
}

Thread pool

9. Add thread pool configuration

The default thread pool does not reuse threads and is a fake thread pool

@Configuration
public class AscyConfig implements AsyncConfigurer {
    /**
     * Configure the thread pool to reduce the time required to create and destroy threads when calling each asynchronous method
     */
    @Override
    public Executor getAsyncExecutor() {
        // Initialize the thread pool provided by the Spring framework
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // Number of core threads
        executor.setCorePoolSize(3);
        // Maximum number of threads
        executor.setMaxPoolSize(3);
        // Task waiting queue size
        executor.setQueueCapacity(10);
        // Task rejection policy: if the thread pool refuses to accept the task, the calling thread is used to execute
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // Define thread name prefix
        executor.setThreadNamePrefix("async-executor-");
        // Call the thread pool initialization method. If the @ Bean annotation is added to getAsyncExecutor(), this method can not be called, because the ThreadPoolTaskExecutor implements the InitializingBean interface, and Spring will call InitializingBean when initializing the Bean afterPropertiesSet()
        executor.initialize();
        return executor;
    }
}

Multiple requests can be found that three threads in the thread pool process in turn

Name specifies the thread pool

10. Multiple thread pools

@Bean("asyncPool")
@Override
public Executor getAsyncExecutor() {
    ...
}

@Bean("asyncPool2")
public Executor asyncPool2() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    return executor;
}

//The asynchronous thread pool with this name is called by default
@Bean("taskExecutor")
public Executor asyncPool3() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    return executor;
}
//Specifies the thread pool name
@Async("asyncPool2")
Future<String> Verification(){...}

When there are multiple @ Async in the project and only some asynchronous thread pools are specified, the rule priority test for asynchronous methods without thread pools is as follows

  1. Override the Excutor returned by getAsyncExecutor of asyncconfigurator
  2. Find the default use name @ Bean("taskExecutor")
  3. Use the original default SimpleAsyncTaskExecutor(SimpleAsyncTaskExecutor is not a real thread pool. This class does not reuse threads, and a new thread will be created every time it is called)

Unified exception capture

  • For asynchronous methods whose return value is Futrue:
    a) One is to catch exceptions when calling get of future;
    b) Catch exceptions directly in exception methods
  • For asynchronous methods whose return value is void: handle exceptions through AsyncUncaughtExceptionHandler

11. Add exception capture of return value void

@Configuration
public class AscyConfig implements AsyncConfigurer {
    /**
     * void Return value asynchronous method exception capture and handling
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncExceptionHandler();
    }

    /**
     * Exception capture processing class
     */
    public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
        @Override
        public void handleUncaughtException(Throwable ex, Method method, Object... params) {
            System.out.println(ex);
        }
    }
}

Component development

Package into components for other project dependencies

12. Add an attribute class so that thread pool parameters can be read from yml

@ConfigurationProperties(prefix = "spring-async.async-thread-pool")
public class AsyncThreadPoolProperties {
    /**
     * Whether to start the asynchronous thread pool. The default is false
     */
    private boolean enable;
    /**
     * Number of core threads, default: number of available threads of Java virtual machine
     */
    private Integer corePoolSize;
    /**
     * Maximum number of threads in thread pool, default: 40000
     */
    private Integer maxPoolSize;
    /**
     * Maximum number of threads in thread queue, default: 80000
     */
    private Integer queueCapacity;
    /**
     * Custom thread name prefix, default: async ThreadPool-
     */
    private String threadNamePrefix;
    /**
     * Maximum idle time of thread in thread pool, default: 60, unit: seconds
     */
    private Integer keepAliveSeconds = 60;
    /**
     * Whether the core thread is allowed to timeout. The default is false
     */
    private boolean allowCoreThreadTimeOut;
    /**
     * IOC Whether to block and wait for the completion of the remaining tasks when the container is closed. Default: false (setAwaitTerminationSeconds must be set)
     */
    private boolean waitForTasksToCompleteOnShutdown;
    /**
     * The closing time of blocking IOC container, default: 10 seconds (setwaitfortastocompleteonshutdown must be set)
     */
    private int awaitTerminationSeconds = 10;
    
    ...get/set.....
}

13. Corresponding yml

# apply name
spring:
  application:
    name: asyncDemo
    
# Application service WEB access port
server:
  port: 8080

#Asynchronous thread pool
#Asynchronous thread pool component switch. The default is false
springAsync:
  asyncThreadPool:
    enable: true
  #Number of core threads, default: number of available threads of Java virtual machine
    core-pool-size: 4
  #Maximum number of threads in thread pool, default: 40000
    max-pool-size: 40000
  #Maximum number of threads in thread queue, default: 80000
    queue-capacity: 80000
  #Custom thread name prefix, default: async ThreadPool-
    thread-name-prefix: Async-ThreadPool-acviti-
  #Maximum idle time of thread in thread pool, default: 60, unit: seconds
    keep-alive-seconds: 60
  #Whether the core thread is allowed to timeout. The default is false
    allow-core-thread-time-out: false
  #Whether to block and wait for the execution of the remaining tasks when the IOC container is closed. Default: false (setAwaitTerminationSeconds must be set)
    wait-for-tasks-to-complete-on-shutdown : false
  #The closing time of blocking IOC container, default: 10 seconds (setwaitfortastocompleteonshutdown must be set)
    await-termination-seconds: 10

14. Modify the configuration class and read the property file and bean loading conditions

@Configuration
@EnableConfigurationProperties(AsyncThreadPoolProperties.class)
@ConditionalOnProperty(prefix = "spring-async.async-thread-pool", name = "enable", havingValue = "true", matchIfMissing = false)
public class AscyConfig implements AsyncConfigurer {

    @Autowired
    private AsyncThreadPoolProperties asyncThreadPoolProperties;
    /**
     * Configure the thread pool to reduce the time required to create and destroy threads when calling each asynchronous method
     */
    @Bean("asyncPool")
    @Override
    public Executor getAsyncExecutor() {
        //Number of processors available for the Java virtual machine
        int processors = Runtime.getRuntime().availableProcessors();
        // Initialize the thread pool provided by the Spring framework
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // Number of core threads
        executor.setCorePoolSize(Objects.nonNull(asyncThreadPoolProperties.getCorePoolSize()) ? asyncThreadPoolProperties.getCorePoolSize() : processors);
        // Maximum number of threads
        executor.setMaxPoolSize(Objects.nonNull(asyncThreadPoolProperties.getCorePoolSize()) ? asyncThreadPoolProperties.getCorePoolSize() : processors);
        // Task waiting queue size
        executor.setQueueCapacity(10);
        // Task rejection policy: if the thread pool refuses to accept the task, the calling thread is used to execute
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // Define thread name prefix
        executor.setThreadNamePrefix(StringUtils.isEmpty(asyncThreadPoolProperties.getThreadNamePrefix()) ? "Async-ThreadPool-" : asyncThreadPoolProperties.getThreadNamePrefix());
        // Call the thread pool initialization method. If the @ Bean annotation is added to getAsyncExecutor(), this method can not be called, because the ThreadPoolTaskExecutor implements the InitializingBean interface, and Spring will call InitializingBean when initializing the Bean afterPropertiesSet()
        // executor.initialize();
        return executor;
    }
    ......
}

15. Create meta-inf / spring The purpose of the factories file is to tell springboot to load the bean, so that other projects will load the bean when it is dependent on other projects after packaging

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.sync.demo.config.AscyConfig

16. Move @ EnableAsync to the configuration class so that other items do not need to add this annotation

@EnableAsync
@Configuration
@EnableConfigurationProperties(AsyncThreadPoolProperties.class)
@ConditionalOnProperty(prefix = "spring-async.async-thread-pool", name = "enable", havingValue = "true", matchIfMissing = false)
public class AscyConfig implements AsyncConfigurer {...}

17. Modify the package plug-in classifier or skip**

  • When the parameters are not modified, the jar packaged by springboot is a jar package that contains the executable dependent jar
  • class will be copied to the new BOOT-INF/classes and displayed in manifest MF specifies the location
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Built-By: Admin
Start-Class: com.sync.demo.DemoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.3.7.RELEASE
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_40
Main-Class: org.springframework.boot.loader.JarLauncher
  • If you rely on this jar package, the class will not be found
  • The jar package that can be relied on is the renamed demo-0.0.1-snapshot jar. original
<!-- Packaged jar Is the original dependability jar,Suffix is no longer renamed to.jar.original-->
<skip>true</skip>
<!-- Packaged jar Is the original dependability jar,Executable jar Renamed,Name stitching exec-->
<classifier>exec</classifier>
<plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.sync.demo.DemoApplication</mainClass>
<!--                    <classifier><skip>Just keep one                    -->
<!--                    <classifier>exec</classifier>                  -->
                        <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

18. After installing into maven library, start another springboot project

<!-- Add dependent coordinates when packaging -->
<dependency>
    <groupId>com.sync</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

19. Configure enable: true to directly use @ Async asynchronous thread pool in the project

springAsync:
  asyncThreadPool:
    enable: true
    #Custom thread name prefix, default: async ThreadPool-
    thread-name-prefix: Async-ThreadPool-acviti-wohaha

Keywords: Spring

Added by domainshuffle on Wed, 09 Feb 2022 13:06:42 +0200