제이온

[Spring] Utilizzo di @Async

Creato: 2024-04-25

Creato: 2024-04-25 22:33

Gestione Asincrona Java

Prima di esaminare Spring @Async, è fondamentale comprendere i concetti di sincrono, asincrono e multithreading. Supponendo che tu conosca questi concetti, diamo un'occhiata al modo in cui la gestione asincrona viene eseguita in Java puro tramite codice. Se hai familiarità con i thread Java, puoi tranquillamente saltare questo capitolo.



Se creassimo una funzione che riceve un messaggio e lo stampa semplicemente in modo sincrono, potremmo scrivere il codice come sopra. Se lo trasformiamo in un metodo asincrono multithreading, possiamo scrivere il codice sorgente come segue.



Tuttavia, questo metodo è molto pericoloso perché non consente di gestire i thread. Ad esempio, se vengono effettuate 10.000 chiamate contemporaneamente, è necessario creare 10.000 thread in un breve lasso di tempo. Il costo della creazione di thread non è trascurabile e può influire negativamente sulle prestazioni del programma, e persino causare un errore OOM. Pertanto, è necessario implementare un pool di thread per gestire i thread e Java fornisce la classe ExecutorService.



Il numero totale di thread è limitato a 10 e siamo riusciti a eseguire correttamente la gestione asincrona desiderata in modalità multithreading. Tuttavia, con questo metodo, è necessario applicare il metodo submit() di ExecutorService a ciascun metodo che si desidera gestire in modo asincrono, il che richiede modifiche ripetitive. 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


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 ha il problema di non gestire i thread. Questo perché la configurazione predefinita di @Async utilizza SimpleAsyncTaskExecutor, che non è un pool di thread, ma semplicemente crea thread.


Utilizzo di un Pool di Thread

Per prima cosa, rimuoviamo @EnableAsync dalla classe Application. Se la classe Application è configurata con @EnableAutoConfiguration o @SpringBootApplication, al momento dell'esecuzione leggerà le informazioni sul bean threadPoolTaskExecutor (che creeremo di seguito) dalla classe SpringAsyncConfig (configurata con @Configuration).



È possibile impostare le dimensioni core e max. In questo caso, potremmo aspettarci che inizi con il numero di thread core e aumenti fino al numero di thread max se non è possibile gestire più operazioni, ma non è così.


Internamente, crea una coda LinkedBlockingQueue di dimensioni Integer.MAX_VALUE, quindi se i thread core non sono in grado di gestire le operazioni, vengono messi in coda. Quando la coda è piena, crea thread fino al numero massimo per gestire le operazioni.


Se non vuoi impostare la dimensione della coda su Integer.MAX_VALUE, puoi impostare queueCapacity. Con la configurazione sopra, inizierà con 3 thread e se la velocità di elaborazione rallenta, metterà le operazioni in una coda di dimensione 100. Se arrivano più richieste, creerà fino a 30 thread per gestire le operazioni.


Una volta completata la configurazione del pool di thread, puoi semplicemente aggiungere il nome del bean al metodo annotato con @Async.



Se desideri impostare diversi tipi di pool di thread, crea più metodi di creazione di bean come threadPoolTaskExecutor() e inserisci il bean del pool di thread desiderato quando imposti @Async.


Forma restituita in base al tipo di ritorno

Nessun valore di ritorno

Questo è il caso in cui il metodo che deve essere elaborato in modo asincrono non ha bisogno di restituire alcun risultato di elaborazione. In questo caso, imposta il tipo di ritorno dell'annotazione @Async su void.


Valore di ritorno presente

Puoi utilizzare i tipi Future, ListenableFuture e CompletableFuture come tipo di ritorno. La forma di ritorno del metodo asincrono può essere racchiusa in new AsyncResult().


[Future]


future.get() blocca e attende il risultato della richiesta. Quindi diventa un metodo di blocco asincrono e le prestazioni non sono buone. In genere, Future non viene utilizzato.


[ListenableFuture]


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



[CompletableFuture]

Anche se ListenableFuture può essere utilizzato per implementare una logica non bloccante, se è necessario un callback all'interno di un callback, può portare a un codice molto complesso chiamato "callback hell".


<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F9f152db8-c015-43cd-bf13-85c594f5f218%2FUntitled.png?table=block&id=268ac0bc-ca7b-4bcb-b11b-ac611a5038b2&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/1455;" width="2000" height="1455"></span>


Naturalmente, non approfondiremo CompletableFuture in questa lezione, quindi se sei curioso di sapere come gestire i callback complessi, consulta il link alla fonte di seguito.



La leggibilità è migliorata rispetto alla definizione di callback di ListenableFuture ed esegue perfettamente la funzione non bloccante. Pertanto, si consiglia di utilizzare CompletableFuture quando si utilizza @Async e si richiede un valore di ritorno.


Vantaggi di @Async

Gli sviluppatori possono scrivere metodi in modo sincrono e, se desiderano che siano asincroni, possono semplicemente aggiungere l'annotazione @Async sopra il metodo. Ciò consente di creare un codice più manutenibile sia in modo sincrono che asincrono.


Precauzioni per @Async

Per utilizzare la funzione @Async, è necessario dichiarare l'annotazione @EnableAsync. Tuttavia, se non si esegue alcuna configurazione aggiuntiva, funzionerà in modalità proxy. In altre parole, tutti i metodi asincroni che utilizzano l'annotazione @Async seguiranno le restrizioni di Spring AOP. Per maggiori dettagli, consulta questo post.


  • Anche se si aggiunge @Async a un metodo privato, AOP non funzionerà.
  • Se i metodi nello stesso oggetto si chiamano a vicenda, AOP non funzionerà.


Fonti

Commenti0