Try using it in your preferred language.

English

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

Esta es una publicación traducida por IA.

제이온

[Spring] Cómo utilizar @Async

Seleccionar idioma

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

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".


Untitled


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.


Fuente

제이온
제이온
제이온
제이온
[Java] Colección sincronizada vs Colección concurrente Analicé comparativamente las diversas formas y ventajas y desventajas para resolver los problemas de sincronización cuando se utiliza una colección en un entorno multihilo en Java. Vector, Hashtable, Collections.synchronizedXXX y otras colecciones sincron

25 de abril de 2024

[Spring] ¿Qué son Filter, Interceptor y Argument Resolver? Aprenda más sobre los conceptos y diferencias de Filter, Interceptor y Argument Resolver en aplicaciones web de Spring. Analice los métodos de implementación, el momento de uso y las ventajas y desventajas de cada función, y comprenda a través de ejempl

27 de abril de 2024

[Effective Java] Item 6. Evita la creación innecesaria de objetos Esta es una guía sobre cómo reducir la creación innecesaria de objetos en Java. Para objetos inmutables como String y Boolean, es mejor usar literales y para expresiones regulares, es mejor almacenar en caché las instancias de Pattern. Además, el autoboxi

28 de abril de 2024

[Concurrencia] Operación atómica: Memory Fence y Memory Ordering Esta publicación de blog explica cómo tener en cuenta el orden de la memoria en las operaciones atómicas y la importancia de las opciones de ordenación. Se explica en detalle las diversas opciones de ordenación, como Relaxed, Acquire, Release, AcqRel y Se
곽경직
곽경직
곽경직
곽경직
곽경직

12 de abril de 2024

[No especializado en informática, sobrevivir como desarrollador] 14. Resumen de las preguntas comunes de la entrevista técnica para desarrolladores principiantes Esta es una guía de preparación para entrevistas técnicas para desarrolladores principiantes. Se explican conceptos que aparecen con frecuencia en las entrevistas, como el área de memoria principal, las estructuras de datos, RDBMS y NoSQL, orientación a p
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

3 de abril de 2024

Modelado de datos físico El modelado de datos físico es el proceso de diseñar las tablas de una base de datos relacional para que sean realmente utilizables. Se busca optimizar el rendimiento mediante la eficiencia del espacio de almacenamiento, el particionamiento de datos, el d
제이의 블로그
제이의 블로그
제이의 블로그
제이의 블로그
제이의 블로그

9 de abril de 2024

¿Qué es el relleno de ranuras (Slot-Filling)? El relleno de ranuras es cuando un chatbot hace preguntas repetidas hasta que obtiene toda la información necesaria del usuario. Por ejemplo, al pedir un café, el chatbot preguntará por el tipo, la temperatura y el tamaño de la taza, y no completará el pe
꿈많은청년들
꿈많은청년들
Imagen con la palabra Slot-Filling en grande
꿈많은청년들
꿈많은청년들

13 de mayo de 2024

Cómo Rust previene los errores de concurrencia Rust es un lenguaje poderoso que resuelve los desafíos de la programación concurrente. Su sistema de tipos y modelo de propiedad hacen que el intercambio y uso compartido de datos entre subprocesos sea seguro. Mediante patrones de mutabilidad interna como
곽경직
곽경직
곽경직
곽경직
곽경직

28 de marzo de 2024

Ideas para mejorar el programa de trading automático Presentamos ideas para mejorar las funciones del programa de automatización de trading de grillas, incluyendo la gestión de grandes eventos, la lógica de gestión de inversiones, la adición de funciones de posición corta, etc. En particular, se explica que
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마

21 de abril de 2024