![translation](https://cdn.durumis.com/common/trans.png)
Questo è un post tradotto da IA.
[Spring] Come usare @Async
- Lingua di scrittura: Coreana
- •
-
Paese di riferimento: Tutti i paesi
- •
- Tecnologia dell'informazione
Seleziona la lingua
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".
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.