![translation](https://cdn.durumis.com/common/trans.png)
Esta es una publicación traducida por IA.
[Spring] Cómo utilizar @Async
- Idioma de escritura: Coreano
- •
-
País de referencia: Todos los países
- •
- Tecnología de la información
Seleccionar idioma
Texto resumido por la IA durumis
- Explica cómo implementar el procesamiento asíncrono de Java utilizando Spring @Async, sus ventajas y precauciones.
- Con Spring @Async, puede implementar fácilmente el procesamiento asíncrono agregando la anotación @EnableAsync y aplicando la anotación @Async a los métodos para los que desea el procesamiento asíncrono.
- La configuración del grupo de subprocesos permite una administración eficiente de los subprocesos y, según el tipo de retorno, se pueden utilizar Future, ListenableFuture y CompletableFuture para devolver los resultados del procesamiento asíncrono.
Procesamiento asíncrono de Java
Antes de analizar Spring @Async, los conceptos de sincronía, asincronía y multihilo son esenciales. Suponiendo que conoce estos conceptos, analicemos el método de procesamiento asíncrono de Java puro a través del código. Si está familiarizado con los hilos de Java, puede omitir este capítulo.
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 + "");
}
}
Si creara una función para recibir un mensaje y simplemente imprimirlo de manera síncrona, podría escribir el código como se muestra arriba. Si lo cambia a un método de procesamiento asíncrono multihilo, el código fuente se puede escribir como se muestra a continuación.
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 + "");
}
}
Sin embargo, este método no permite administrar los hilos, lo que lo hace muy peligroso. Por ejemplo, si se realizan 10,000 llamadas al mismo tiempo, se deben crear 10,000 hilos en un tiempo muy corto. El costo de crear un hilo no es bajo, por lo que afecta negativamente al rendimiento del programa, e incluso puede causar un error OOM. Por lo tanto, para administrar los hilos, debe implementar un grupo de hilos, y Java proporciona la clase ExecutorService.
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 + "");
}
}
Hemos limitado el número total de hilos a 10 y ahora podemos realizar el procesamiento asíncrono multihilo que deseamos correctamente. Sin embargo, este método requiere aplicar el método submit() de ExecutorService a cada método que desea procesar de manera asíncrona. por lo que debe realizar trabajo de modificación repetitivo. Es decir, si inicialmente desea cambiar un método escrito con lógica síncrona a asíncrono, es inevitable un cambio en la lógica del método en sí.
Spring @Async
Método simple
@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 + "");
}
}
Solo tiene que colocar la anotación @EnableAsync sobre la clase Application y colocar la anotación @Async sobre el método de lógica síncrona que desea procesar de manera asíncrona. Sin embargo, este método tiene el problema de que no administra los hilos. Esto se debe a que la configuración predeterminada de @Async está configurada para usar SimpleAsyncTaskExecutor, que no es un grupo de hilos, sino que simplemente crea hilos.
Método para usar un grupo de hilos
Primero, elimine @EnableAsync de la clase Application. Si la clase Application tiene la configuración @EnableAutoConfiguration o @SpringBootApplication, leerá la información del bean threadPoolTaskExecutor (que se creará a continuación) de la clase SpringAsyncConfig configurada con @Configuration en tiempo de ejecución.
@Configuration
@EnableAsync // Debe colocarse en la clase de configuración asíncrona, no en Application.
public class SpringAsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(3); // Número de hilos predeterminado
taskExecutor.setMaxPoolSize(30); // Número máximo de hilos
taskExecutor.setQueueCapacity(100); // Tamaño de la cola
taskExecutor.setThreadNamePrefix("Executor-");
return taskExecutor;
}
Puede configurar el tamaño del núcleo y el tamaño máximo. En este caso, puede esperar que funcione con el tamaño del núcleo inicial y que el número de hilos aumente hasta el tamaño máximo si no puede procesar más trabajo, pero no es así.
En el interior, crea un LinkedBlockingQueue con un tamaño de Integer.MAX_VALUE y espera en la cola si los hilos del tamaño del núcleo no pueden procesar el trabajo. Si la cola se llena, se crean hilos hasta el tamaño máximo para procesar el trabajo.
Si le resulta engorroso configurar el tamaño de la cola como Integer.MAX_VALUE, puede configurar queueCapacity. Si configura como se muestra arriba, funcionará con 3 hilos inicialmente y esperará en la cola de tamaño 100 si la velocidad de procesamiento es lenta. Si se recibe una solicitud adicional, se crean hasta 30 hilos para procesar el trabajo.
Una vez completada la configuración del grupo de hilos, solo tiene que colocar el nombre del bean en el método anotado con @Async.
@Service
public class MessageService {
@Async("threadPoolTaskExecutor")
public void print(String message) {
System.out.println(message);
}
Si desea configurar varios tipos de grupos de hilos, puede crear varios métodos de creación de beans como threadPoolTaskExecutor() y insertar el bean del grupo de hilos que desee al configurar @Async.
Formato devuelto por tipo de retorno
Sin valor de retorno
Este es el caso en el que el método que debe procesarse de manera asíncrona no necesita entregar el resultado del procesamiento. En este caso, puede configurar el tipo de retorno de la anotación @Async como void.
Con valor de retorno
Puede usar los tipos Future, ListenableFuture y CompletableFuture como tipo de retorno. Puede encapsular el formato de retorno del método asíncrono en 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());
}
}
}
// Resultado de la ejecución
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() realiza el bloqueo y espera hasta que llega el resultado de la solicitud. Por lo tanto, se convierte en un método de bloqueo asíncrono y su rendimiento no es bueno. Generalmente, no se utiliza Future.
[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()));
}
}
}
// Resultado de la ejecución
Task Start - 1
Task Start - 3
Task Start - 2
jayon-1
jayon-2
Task Start - 5
jayon-3
Task Start - 4
jayon-4
ListenableFuture permite procesar el trabajo de manera no bloqueante a través de una devolución de llamada. El primer parámetro del método addCallback() es el método de devolución de llamada de trabajo completado, y el segundo parámetro es el método de devolución de llamada de error de trabajo definido. Como referencia, dado que el hilo principal del grupo de hilos se configuró en 3, puede ver que el mensaje "Inicio de la tarea" se imprime inicialmente 3 veces.
[CompletableFuture]
Incluso con ListenableFuture solo, puede implementar la lógica no bloqueante, pero si necesita una devolución de llamada dentro de una devolución de llamada, provoca un código muy complejo, llamado "infierno de devoluciones de llamada".
Por supuesto, en este momento no cubriremos CompletableFuture en detalle, por lo que si le interesa el código que maneja las devoluciones de llamada complejas, consulte el enlace de origen a continuación.
@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;
});
}
}
La legibilidad ha mejorado en comparación con la definición de devolución de llamada de ListenableFuture, y realiza la función no bloqueante completamente. Por lo tanto, se recomienda usar CompletableFuture cuando use @Async y necesite un valor de retorno.
Ventajas de @Async
Los desarrolladores pueden escribir métodos de manera síncrona y simplemente colocar la anotación @Async sobre el método si desean un método asíncrono. Por lo tanto, puede crear un código que es fácil de mantener para la sincronización y la asincronía.
Precauciones al usar @Async
Para utilizar la función @Async, debe declarar la anotación @EnableAsync, pero si no realiza ninguna configuración adicional, funcionará en modo proxy. Es decir, los métodos asíncronos que funcionan con la anotación @Async seguirán todas las restricciones de Spring AOP. Para obtener más información, consulteesta publicación.
- AOP no funciona incluso si coloca @Async en un método privado.
- AOP no funciona cuando se llama a métodos dentro del mismo objeto.