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

Questo è un post tradotto da IA.

제이온

[Spring] Come usare @Async

Seleziona la lingua

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

Testo riassunto dall'intelligenza artificiale durumis

  • Spiega come implementare la gestione asincrona di Java utilizzando Spring @Async, i suoi vantaggi e le precauzioni.
  • Con Spring @Async, puoi implementare facilmente la gestione asincrona aggiungendo l'annotazione @EnableAsync e aggiungendo l'annotazione @Async al metodo per cui desideri la gestione asincrona.
  • La configurazione del pool di thread consente una gestione efficiente dei thread e, a seconda del tipo di ritorno, è possibile utilizzare Future, ListenableFuture, CompletableFuture e altri per restituire i risultati del trattamento asincrono.

Elaborazione asincrona Java

Prima di esaminare Spring @Async, è essenziale comprendere i concetti di sincrono, asincrono e multithreading. Supponendo di conoscere questi concetti, esaminiamo come gestire l'elaborazione asincrona in Java puro tramite codice. Se hai familiarità con i thread Java, puoi saltare questo capitolo.


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 + "");
        }
    }


Se si crea una funzione che riceve un messaggio e lo stampa in modo sincrono, si può scrivere il codice come sopra. Per convertirlo in un metodo asincrono multithreading, il codice sorgente può essere scritto come segue.


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 + "");
        }
    }


Tuttavia, questo metodo non consente di gestire i thread, il che è molto rischioso. Ad esempio, se vengono effettuate 10.000 chiamate contemporaneamente, è necessario creare 10.000 thread in un tempo molto breve. Il costo della creazione di thread non è trascurabile, quindi influisce sulle prestazioni del programma e può persino causare errori OOM. Pertanto, è necessario implementare un pool di thread per gestire i thread e Java fornisce la classe 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 + "");
        }
    }


Il numero totale di thread è limitato a 10 e siamo in grado di eseguire correttamente l'elaborazione asincrona multithreading che desideriamo. Tuttavia, con questo metodo, è necessario applicare il metodo submit() di ExecutorService a ogni metodo che si desidera elaborare in modo asincrono, quindi è necessario apportare modifiche ripetute. In altre parole, se si desidera modificare un metodo inizialmente scritto in modo sincrono in asincrono, è inevitabile modificare la logica del metodo stesso.


Spring @Async

Metodo semplice

@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 + "");
        }
    }


Basta aggiungere l'annotazione @EnableAsync alla classe Application e l'annotazione @Async al metodo della logica sincrona che si desidera gestire in modo asincrono. Tuttavia, questo metodo non gestisce i thread. Questo perché la configurazione predefinita di @Async è quella di utilizzare SimpleAsyncTaskExecutor, che non è un pool di thread, ma semplicemente crea thread.


Metodo di utilizzo del pool di thread

Innanzitutto, rimuovere @EnableAsync dalla classe Application. Se la classe Application ha una configurazione @EnableAutoConfiguration o @SpringBootApplication, al momento dell'esecuzione leggerà le informazioni sul bean threadPoolTaskExecutor dalla classe SpringAsyncConfig (che verrà creata di seguito) in cui è impostata la configurazione @Configuration.


@Configuration
@EnableAsync // Deve essere aggiunto alla classe di configurazione asincrona, non all'applicazione.
public class SpringAsyncConfig {

    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(3); // Numero di thread di base
        taskExecutor.setMaxPoolSize(30); // Numero massimo di thread
        taskExecutor.setQueueCapacity(100); // Dimensione della coda
        taskExecutor.setThreadNamePrefix("Executor-");
        return taskExecutor;
    }


È possibile impostare le dimensioni di core e max. In questo caso, si potrebbe presumere che inizialmente funzionerà con le dimensioni di core e che il numero di thread aumenterà fino alle dimensioni di max se non è possibile elaborare più attività. Tuttavia, non è così.


Internamente, viene creata una LinkedBlockingQueue delle dimensioni Integer.MAX_VALUE e, se i thread core non riescono a elaborare le attività, verranno messi in coda. Quando la coda è piena, verrà creato un thread della dimensione max per l'elaborazione.


Se non si desidera impostare la dimensione della coda su Integer.MAX_VALUE, è possibile impostare queueCapacity. Con la configurazione sopra, inizialmente verranno elaborati 3 thread e, se la velocità di elaborazione si riduce, le attività verranno inserite in una coda di dimensioni 100. Se vengono ricevute ulteriori richieste, verranno creati fino a 30 thread per l'elaborazione delle attività.


Una volta completata la configurazione del pool di thread, è sufficiente aggiungere il nome del bean al metodo a cui è stata applicata l'annotazione @Async.


@Service
public class MessageService {

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


Se si desidera impostare più tipi di pool di thread, è possibile creare più metodi di generazione di bean come threadPoolTaskExecutor() e inserire il bean del pool di thread desiderato durante la configurazione di @Async.


Forma restituita in base al tipo di ritorno

Nessun valore restituito

Questo è il caso in cui il metodo da elaborare in modo asincrono non ha bisogno di trasmettere i risultati dell'elaborazione. In questo caso, impostare il tipo di ritorno dell'annotazione @Async su void.


Valore restituito

È possibile utilizzare i tipi Future, ListenableFuture, CompletableFuture come tipo di ritorno. La forma di ritorno del metodo asincrono può essere racchiusa 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());
        }
    }
}

// Risultato dell'esecuzione
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() svolge il ruolo di attendere fino a quando non arriva il risultato della richiesta tramite il blocco. Pertanto, diventa un metodo di blocco asincrono, il che non è molto efficiente. In genere, Future non viene utilizzato.


[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()));
        }
    }
}

// Risultato dell'esecuzione
Task Start - 1
Task Start - 3
Task Start - 2
jayon-1
jayon-2
Task Start - 5
jayon-3
Task Start - 4
jayon-4


ListenableFuture consente di elaborare attività in modo non bloccante tramite i callback. Il primo parametro del metodo addCallback() è il metodo di callback di completamento dell'attività e il secondo parametro è il metodo di callback di errore dell'attività. Per inciso, poiché il thread core del pool di thread è impostato su 3, è possibile vedere che i messaggi "Task Start" vengono visualizzati inizialmente 3 volte.



[CompletableFuture]

Anche ListenableFuture può implementare la logica non bloccante, ma se è necessario un callback all'interno di un callback, si può finire per scrivere codice molto complesso, noto come "inferno dei callback".


Untitled


Naturalmente, non tratteremo CompletableFuture in dettaglio in questa sessione, quindi se sei curioso di conoscere il codice che gestisce i callback complessi, ti invito a consultare il link fornito di seguito.


@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 leggibilità è migliore rispetto alla definizione del callback di ListenableFuture e le funzionalità non bloccanti vengono eseguite completamente. Pertanto, è consigliabile utilizzare CompletableFuture quando si utilizza @Async e si ha bisogno di un valore restituito.


Vantaggi di @Async

Gli sviluppatori possono scrivere metodi in modo sincrono e, se desiderano un metodo asincrono, possono semplicemente aggiungere l'annotazione @Async sopra il metodo. Pertanto, è possibile creare codice di facile manutenzione per il sincrono e l'asincrono.


Avvertenze su @Async

Per utilizzare la funzionalità @Async, è necessario dichiarare l'annotazione @EnableAsync. Se non si imposta alcuna configurazione separata, funzionerà in modalità proxy. In altre parole, tutti i metodi asincroni che funzionano con l'annotazione @Async rispetteranno le restrizioni di Spring AOP. Per ulteriori informazioni, consultareQuesto post.


  • Anche se si applica @Async a un metodo privato, AOP non funziona.
  • AOP non funziona quando si chiamano metodi tra loro nello stesso oggetto.


Fonti

제이온
제이온
제이온
제이온
[Java] Synchronized Collection vs Concurrent Collection In Java, we compared and analyzed various methods and advantages and disadvantages for solving synchronization problems when using collections in a multithreaded environment. We introduce the characteristics and performance differences of synchronized col

25 aprile 2024

[Spring] Che cos'è Filter, Interceptor, Argument Resolver? Scopri di più sui concetti e sulle differenze tra Filter, Interceptor e Argument Resolver nelle applicazioni web Spring. Scopri come implementare ciascuna funzione, quando usarla, i pro e i contro e usa esempi di codice reali per comprenderli. Inoltre,

27 aprile 2024

[Effictive Java] Item 6. Evitare la creazione di oggetti non necessari Questa è una guida su come ridurre la creazione di oggetti non necessari in Java. Per gli oggetti immutabili come String e Boolean, è meglio usare i letterali e per le espressioni regolari è meglio mettere in cache l'istanza di Pattern. Inoltre, l'autobox

28 aprile 2024

[Concurrency] Operazione Atomica: Memory Fence e Memory Ordering Questo post del blog spiega come considerare l'ordine di memoria nelle operazioni atomiche e l'importanza delle opzioni di ordinamento. Vengono spiegate le diverse opzioni di ordinamento come Relaxed, Acquire, Release, AcqRel, SecCst, insieme ai vantaggi
곽경직
곽경직
곽경직
곽경직
곽경직

12 aprile 2024

[Non specialisti, sopravvivere come sviluppatori] 14. Riepilogo dei contenuti del colloquio tecnico per sviluppatori junior Questa è una guida alla preparazione ai colloqui tecnici per sviluppatori junior. Copre argomenti come la memoria principale, le strutture dati, RDBMS e NoSQL, programmazione procedurale e orientata agli oggetti, override e overload, algoritmi di sostituz
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

3 aprile 2024

Come Rust impedisce i bug di concorrenza Rust è un linguaggio potente che affronta le sfide della programmazione concorrente. Il suo sistema di tipi e il modello di proprietà garantiscono la sicurezza nella condivisione e nel trasferimento di dati tra thread. Tramite pattern di mutabilità intern
곽경직
곽경직
곽경직
곽경직
곽경직

28 marzo 2024

Struttura di base del trading automatico di azioni (in aggiornamento...) Spiega in dettaglio passo dopo passo il processo di sviluppo di un programma di trading automatico di azioni, dalla creazione di un conto alla preparazione dell'ambiente di sviluppo, dall'integrazione API alla progettazione dell'interfaccia utente, dall'i
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마

22 aprile 2024

[Storia degli sviluppatori SI] 12. Stack tecnologico comunemente utilizzato nei progetti SI Gli sviluppatori SI in Corea del Sud utilizzano principalmente stack tecnologici come Spring basato su Java, database Oracle, Mybatis, JSP, JavaScript, HTML, CSS per sviluppare sistemi IT efficienti e stabili, utilizzando Eclipse come ambiente di sviluppo
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

19 aprile 2024

Sviluppo dell'API di Korea Investment Securities: difficoltà incontrate Questo post del blog tratta in dettaglio le difficoltà incontrate durante lo sviluppo dell'API di Korea Investment Securities e le relative soluzioni. Condivide l'esperienza e i suggerimenti di uno sviluppatore in merito all'apertura di un conto, al trasf
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마

23 aprile 2024