translation

This is an AI translated post.

제이온

[Spring] How to Use @Async

Select Language

  • English
  • 汉语
  • Español
  • Bahasa Indonesia
  • Português
  • Русский
  • 日本語
  • 한국어
  • Deutsch
  • Français
  • Italiano
  • Türkçe
  • Tiếng Việt
  • ไทย
  • Polski
  • Nederlands
  • हिन्दी
  • Magyar

Summarized by durumis AI

  • This document describes how to implement Java asynchronous processing using Spring @Async, its advantages, and precautions.
  • Spring @Async allows you to easily implement asynchronous processing by simply adding the @EnableAsync annotation and attaching the @Async annotation to the methods you want to process asynchronously.
  • Efficient thread management is possible through thread pool settings, and you can return asynchronous processing results using Future, ListenableFuture, and CompletableFuture, depending on the return type.

Java Asynchronous Processing

Before we look at Spring @Async, it is essential to understand the concepts of synchronous, asynchronous, and multi-threaded. Assuming you are familiar with these concepts, let's take a look at pure Java asynchronous processing methods in code. If you are familiar with Java threads, you can skip this chapter.


public class MessageService {

    public void print(String message) {
        System.out.println(message);
    }
}

public class Main {

    public static void main(String[] args) {
        MessageService messageService = new MessageService();

        for (int i = 1; i <= 100; i++) {
            messageService.print(i + "");
        }
    }


If you create a function that simply receives and prints a message synchronously, you can write code as shown above. If you change this to a multi-threading asynchronous method, you can write the source code as follows.


public class MessageService {

    public void print(String message) {
        new Thread(() -> System.out.println(message))
                .start();
    }
}

public class Main {

    public static void main(String[] args) {
        MessageService messageService = new MessageService();

        for (int i = 1; i <= 100; i++) {
            messageService.print(i + "");
        }
    }


However, this method is very risky because you cannot manage the threads. For example, if 10,000 calls are made at the same time, you need to create 10,000 threads in a very short time. The cost of creating threads is not small, so it can negatively affect the performance of the program, and it can even cause an OOM error. Therefore, you need to implement a Thread Pool to manage the threads, and Java provides the ExecutorService class.


public class MessageService {

    private final ExecutorService executorService = Executors.newFixedThreadPool(10);

    public void print(String message) {
        executorService.submit(() -> System.out.println(message));
    }
}

public class Main {

    public static void main(String[] args) {
        MessageService messageService = new MessageService();

        for (int i = 1; i <= 100; i++) {
            messageService.print(i + "");
        }
    }


The total number of threads is limited to 10, and we are now able to correctly perform the asynchronous processing in the multi-threading method that we want. However, with this method, you have to apply the ExecutorService's submit() method to each method you want to process asynchronously, so you have to do repetitive modification work. In other words, if you want to change a method originally written in synchronous logic to asynchronous, you will inevitably have to change the logic of the method itself.


Spring @Async

Simple Method

@EnableAsync
@SpringBootApplication
public class DemoApplication {

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

}

@Service
public class MessageService {

    @Async
    public void print(String message) {
        System.out.println(message);
    }
}

@RequiredArgsConstructor
@RestController
public class MessageController {

    private final MessageService messageService;

    @GetMapping("/messages")
    @ResponseStatus(code = HttpStatus.OK)
    public void printMessage() {
        for (int i = 1; i <= 100; i++) {
            messageService.print(i + "");
        }
    }


Just attach the @EnableAsync annotation to the Application class and attach the @Async annotation to the method of the synchronous logic you want to process asynchronously. However, this method has the problem of not managing threads. This is because the default setting of @Async is to use SimpleAsyncTaskExecutor, which is not a thread pool, but simply creates threads.


Using Thread Pool

First, remove @EnableAsync from the Application class. If the Application class has @EnableAutoConfiguration or @SpringBootApplication settings, it reads the threadPoolTaskExecutor bean information of the SpringAsyncConfig class (which will be created below) at runtime because the @Configuration is set.


@Configuration
@EnableAsync // Should be attached to Async configuration class, not Application.
public class SpringAsyncConfig {

    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(3); // Default thread count
        taskExecutor.setMaxPoolSize(30); // Maximum thread count
        taskExecutor.setQueueCapacity(100); // Queue size
        taskExecutor.setThreadNamePrefix("Executor-");
        return taskExecutor;
    }


You can set the core and max sizes. At this time, you might expect that it will operate with the initial core size and then increase the number of threads to the max size if it cannot handle more tasks, but that is not the case.


Internally, it creates a LinkedBlockingQueue of Integer.MAX_VALUE size and waits in the Queue if it cannot handle tasks with the core size of threads. When the Queue is full, it creates threads up to the max size to handle the tasks.


If you find it burdensome to set the Queue size to Integer.MAX_VALUE, you can set queueCapacity. If you set it as above, it will initially process with 3 threads, and if the processing speed slows down, it will put tasks into a 100-size Queue, and if more requests come in, it will create up to 30 threads to handle the tasks.


Once the thread pool is set up, you can attach the name of the bean to the method with the @Async annotation.


@Service
public class MessageService {

    @Async("threadPoolTaskExecutor")
    public void print(String message) {
        System.out.println(message);
    }


If you want to set up multiple thread pool types, create multiple bean creation methods like threadPoolTaskExecutor() and put the thread pool bean you want when setting @Async.


Return Types by Return Type

When there is no return value

This is the case where the method that needs to be processed asynchronously does not need to return a result. In this case, you can set the return type of the @Async annotation to void.


When there is a return value

You can use Future, ListenableFuture, CompletableFuture types as the return type. The return type of the asynchronous method can be wrapped in new AsyncResult().


[Future]

@Service
public class MessageService {

    @Async
    public Future print(String message) throws InterruptedException {
        System.out.println("Task Start - " + message);
        Thread.sleep(3000);
        return new AsyncResult<>("jayon-" + message);
    }
}

@RequiredArgsConstructor
@RestController
public class MessageController {

    private final MessageService messageService;

    @GetMapping("/messages")
    @ResponseStatus(code = HttpStatus.OK)
    public void printMessage() throws ExecutionException, InterruptedException {
        for (int i = 1; i <= 5; i++) {
            Future future = messageService.print(i + "");
            System.out.println(future.get());
        }
    }
}

// Execution Result
Task Start - 1
jayon-1
Task Start - 2
jayon-2
Task Start - 3
jayon-3
Task Start - 4
jayon-4
Task Start - 5


future.get() acts as a blocking function that waits until the request result arrives. Therefore, it becomes an asynchronous blocking method and its performance is not good. Future is usually not used.


[ListenableFuture]

@Service
public class MessageService {

    @Async
    public ListenableFuture print(String message) throws InterruptedException {
        System.out.println("Task Start - " + message);
        Thread.sleep(3000);
        return new AsyncResult<>("jayon-" + message);
    }
}

@RequiredArgsConstructor
@RestController
public class MessageController {

    private final MessageService messageService;

    @GetMapping("/messages")
    @ResponseStatus(code = HttpStatus.OK)
    public void printMessage() throws InterruptedException {
        for (int i = 1; i <= 5; i++) {
            ListenableFuture listenableFuture = messageService.print(i + "");
            listenableFuture.addCallback(System.out::println, error -> System.out.println(error.getMessage()));
        }
    }
}

// Execution Result
Task Start - 1
Task Start - 3
Task Start - 2
jayon-1
jayon-2
Task Start - 5
jayon-3
Task Start - 4
jayon-4


ListenableFuture allows you to process tasks in a non-blocking manner through callbacks. You can define the first parameter of the addCallback() method as the task completion callback method and the second parameter as the task failure callback method. By the way, since the core thread of the thread pool is set to 3, you can see that the "Task Start" message is printed 3 times at the beginning.



[CompletableFuture]

Even with ListenableFuture alone, you can implement non-blocking logic, but if callbacks are needed inside callbacks, it can lead to very complicated code called callback hell.


Untitled


Of course, since we are not going to cover CompletableFuture in detail this time, if you are curious about the code that handles complex callbacks, please refer to the link in the source below.


@Service
public class MessageService {

    @Async
    public CompletableFuture print(String message) throws InterruptedException {
        System.out.println("Task Start - " + message);
        Thread.sleep(3000);
        return new AsyncResult<>("jayon-" + message).completable();
    }
}

@RequiredArgsConstructor
@RestController
public class MessageController {

    private final MessageService messageService;

    @GetMapping("/messages")
    @ResponseStatus(code = HttpStatus.OK)
    public void printMessage() throws InterruptedException {
        for (int i = 1; i <= 5; i++) {
            CompletableFuture completableFuture = messageService.print(i + "");
            completableFuture
                    .thenAccept(System.out::println)
                    .exceptionally(error -> {
                        System.out.println(error.getMessage());
                        return null;
                    });
        }
    }


It is more readable than defining callbacks for ListenableFuture, and it fully performs the non-blocking function. Therefore, it is recommended to use CompletableFuture when using @Async and a return value is required.


Advantages of @Async

Developers can simply attach the @Async annotation to the method if they want asynchronous behavior, even though they wrote the method synchronously. This allows you to create code that is well-maintained for synchronous and asynchronous operations.


Cautions of @Async

To use the @Async function, you need to declare the @EnableAsync annotation. If you don't make any separate settings, it works in proxy mode. That is, all asynchronous methods that work with the @Async annotation follow the restrictions of Spring AOP. For more details, please refer to this post.


  • Even if you attach @Async to a private method, AOP will not work.
  • AOP does not work when methods within the same object call each other.


Source

제이온
제이온
제이온
제이온
[Spring] What are Filter, Interceptor, and Argument Resolver? Learn in detail about the concepts and differences between Filter, Interceptor, and Argument Resolver that handle requests in Spring web applications. This article provides a comparative analysis of the implementation methods, timing of use, advantages an

April 27, 2024

[Java] Synchronized Collection vs Concurrent Collection This article compares and analyzes various methods and their pros and cons for solving synchronization issues when using collections in a multithreaded environment in Java. It introduces the characteristics and performance differences between synchronized

April 25, 2024

[Effective Java] Item 1: Consider Static Factory Methods Instead of Constructors Static factory methods provide a flexible and efficient way to create instances instead of constructors. They can have names, return instances that meet specific conditions, and improve performance through caching. Unlike the singleton pattern, they can c

April 27, 2024

[Non-Computer Science, Surviving as a Developer] 14. Summary of Frequently Asked Technical Interview Questions for New Developers This is a technical interview preparation guide for new developers. It explains concepts frequently encountered in interviews such as the main memory area, data structures, RDBMS and NoSQL, procedural and object-oriented, overriding and overloading, page
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

April 3, 2024

[SI Developer Story] 03. How to prepare for an SI company interview SI developer interviews focus more on coding ability than high technical skills, so understanding the Spring + mybatis structure learned at a government-funded training institute is sufficient. Most interviews do not include coding tests, and the decision
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

April 16, 2024

For those who want to give good feedback Do you want to learn effective feedback methods for team member growth? This article emphasizes the importance of 'feedforward', which discovers and praises team members' strengths, and suggests ways to improve team capabilities through honest and positiv
울림
울림
울림
울림

March 18, 2024

How Rust Prevents Concurrency Bugs Rust is a powerful language that tackles the challenges of concurrent programming. Its type system and ownership model ensure safe data transfer and sharing between threads. Internal variability patterns like Mutex, Channel, and Atomic allow you to define
곽경직
곽경직
곽경직
곽경직
곽경직

March 28, 2024

[python] Python Basics 1: Understanding Python Modules Python modules are files that contain variables, functions, and classes, and are useful when using modules created by others or when grouping commonly used variables, functions, etc. You can use the `import` keyword to import and use modules, and you can
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

March 27, 2024

[SI Developer Story] 12. Technology Stack Frequently Used in SI Projects SI developers in South Korea mainly use technology stacks such as Java-based Spring, Oracle DB, Mybatis, JSP, JavaScript, HTML, CSS, to develop efficient and stable IT systems, using Eclipse as their development environment. These technologies contribute
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

April 19, 2024