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à.
Commenti0