SpringBoot integrates thread pools using @ Async

Use steps

  1. Custom application YML configuration items and their values
  2. Create a thread pool configuration class
  3. Create two interfaces OrderService and AsyncOrderService and their implementation classes
  4. Create Controller for testing
  5. Observe the order and effect of the printed content on the console
    5.1 Use @ Async
    5.5 Don't use @ Async

Case requirements: after the user places an order, the server generates an order and saves it, and then sends a text message and e-mail to the user to notify the success of the order
Because sending SMS and e-mail is time-consuming and second level, asynchronous mode should be adopted. There are many implementation schemes. Here, the ThreadPoolTaskExecutor, a thread pool encapsulated by ThreadPoolExecutor provided by Spring, can be used directly on the business method with the annotation @ Async (start with @ EnableAsync first)

The execution rules of thread pool ThreadPoolExecutor are as follows

1. Customize the application YML configuration items and their values

It corresponds to the parameter of creating thread pool. See the member attribute note in step 2 for the specific meaning. The parameter can be modified according to the actual situation. It is only used for demonstration here

async:
  # Asynchronous order task thread pool parameters
  order-service:
    core-pool-size: 20
    max-pool-size: 100
    keep-alive-seconds: 10
    queue-capacity: 200
    thread-name-prefix: async-order-service-

2. Write thread pool configuration class

@Setter
@ConfigurationProperties(prefix = "async.order-service")
@EnableAsync
@Configuration
public class AsyncOrderServiceConfig {
    /**
     * Number of core threads (default threads)
     */
    private int corePoolSize;
    /**
     * Maximum number of threads
     */
    private int maxPoolSize;
    /**
     * Allowed thread idle time (unit: default is seconds)
     */
    private int keepAliveSeconds;
    /**
     * Buffer queue size
     */
    private int queueCapacity;
    /**
     * Thread pool name prefix
     */
    private String threadNamePrefix;

    @Bean
    public ThreadPoolTaskExecutor asyncOrderService() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
        threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
        threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
        threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
        threadPoolTaskExecutor.setThreadNamePrefix(threadNamePrefix);
        // Processing strategy of thread pool for rejecting tasks
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // When the task is completed, it will be closed automatically. The default value is false
        threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        // The core thread timed out and exited. The default value is false
        threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
}

First, support asynchronous tasks through @ EnableAsync, and then use @ ConfigurationProperties to automatically encapsulate similar configuration information into an entity class. Its attribute prefix represents application The prefix of the configuration item in the YML configuration file, combined with Lombok's @ Setter, ensures that the Value of the configuration file can be injected into the corresponding attribute of this class. Of course, you can use @ Value without @ Setter, but it is cumbersome here (you need to add @ Value(${xxx}) to each attribute)

This step can be omitted because when @ Async is used without specifying the value attribute, SpringBoot will create a SimpleAsyncTaskExecutor thread pool by default to handle asynchronous method calls
The annotation @ Async is defined as follows, with only one value attribute (SpringBoot 2.3.7 RELEASE)

3. Create two interfaces OrderService and AsyncOrderService and their implementation classes

Create order business interface

public interface OrderService {
    /**
     * Place an order
     */
    void makeOrder();

    /**
     * Generate order and save to database
     */
    void saveOrder();
}

Create asynchronous order business interface

public interface AsyncOrderService {
    /**
     * SMS service
     */
    void sendSms();

    /**
     * mail serve
     */
    void sendEmail();
}

Create implementation classes and write simple logic instead of business logic

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private AsyncOrderService asyncOrderService;

    @Override
    public void makeOrder() {
        // send message
        asyncOrderService.sendSms();
        // send emails
        asyncOrderService.sendEmail();
        // Save order
        saveOrder();
    }

    @Override
    public void saveOrder() {
        log.info("Generate order and save...");
    }
}
@Slf4j
@Service
public class AsyncOrderServiceImpl implements AsyncOrderService {
    @Async("asyncOrderService")
    @Override
    public void sendSms() {
        try {
            long start = System.currentTimeMillis();
            log.info("Start sending text messages");
            // The simulation takes 3s
            TimeUnit.SECONDS.sleep(3);
            log.info("SMS sent!");
            log.info("Sending SMS takes:{} ms", System.currentTimeMillis() - start);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Async("asyncOrderService")
    @Override
    public void sendEmail() {
        try {
            long start = System.currentTimeMillis();
            log.info("Start sending mail");
            // The simulation takes 2s
            TimeUnit.SECONDS.sleep(2);
            log.info("Mail sent!");
            log.info("Time taken to send mail:{} ms", System.currentTimeMillis() - start);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

The @ Async annotation is used here. It only has a value attribute of String type, which is used to specify the Name of a Bean. The type is Executor or TaskExecutor, which means that the specified thread pool is used to execute asynchronous tasks. Here, it is specified to configure the class asyncOrderService for the thread pool in step 2

There are several points to note, which will invalidate @ Async

● if the SpringBoot framework is used, the @ EnableAsync annotation must be added to the startup class
● asynchronous methods cannot be in the same class as the called asynchronous methods
● asynchronous classes do not use @ Component annotation (or other similar annotations), which makes spring unable to scan asynchronous classes
● annotations such as @ Autowired or @ Resource need to be automatically injected into the class, and new objects cannot be manually injected
● asynchronous methods use non public or static modifiers

4. Create Controller for testing

@RestController
public class TestController {
    @Autowired
    private OrderService orderService;

    @PostMapping("/makeOrder")
    public String makeOrder() {
        orderService.makeOrder();
        return "ok";
    }
}

5. Observe the order in which the console prints logs when @ Async is used and not used

Calling an interface using a modal tool http://localhost:8080/makeOrder , console contents are as follows

Use @ Async
Don't use @ Async

If you need to compare the viewing effect of dynamic graph, it is recommended to view it immediately after refreshing the page, and the effect is the best
Running effects with @ Async

Run effect without @ Async

From the perspective of code logic, the former is asynchronous execution and the latter is synchronous execution;
In terms of execution effect, the completion speed of @ Async is significantly improved

Keywords: Java Spring Boot

Added by discomatt on Thu, 23 Dec 2021 11:55:07 +0200