![translation](https://cdn.durumis.com/common/trans.png)
Dit is een door AI vertaalde post.
[Spring] @Async gebruiken
- Taal van de tekst: Koreaans
- •
-
Referentieland: Alle landen
- •
- Informatietechnologie
Selecteer taal
Samengevat door durumis AI
- Deze handleiding legt uit hoe je Java-asynchrone verwerking kunt implementeren met behulp van Spring @Async, inclusief de voordelen en aandachtspunten.
- Met Spring @Async kun je eenvoudig asynchrone verwerking implementeren door de @EnableAsync-annotatie toe te voegen en de @Async-annotatie toe te passen op de methoden die je asynchroon wilt verwerken.
- Efficiënt threadbeheer is mogelijk via thread-poolinstellingen, en je kunt asynchrone verwerkingsresultaten teruggeven met behulp van Future, ListenableFuture, CompletableFuture, afhankelijk van het retourtype.
Java asynchrone verwerking
Voordat we Spring @Async bekijken, zijn de concepten van synchronisatie, asynchrone en multithreading essentieel. Aangenomen dat u bekend bent met deze concepten, laten we eens kijken naar pure Java asynchrone verwerking met code. Als u bekend bent met Java threads, kunt u deze sectie overslaan.
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 + "");
}
}
Als we een functie maken die een bericht ontvangt en het eenvoudigweg afdrukt met een synchrone methode, kunnen we de code schrijven zoals hierboven. Als we deze omzetten naar een multithreaded asynchrone methode, kunnen we de broncode schrijven zoals hieronder.
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 + "");
}
}
Maar deze methode is erg riskant omdat we threads niet kunnen beheren. Als er bijvoorbeeld tegelijkertijd 10.000 aanvragen worden gedaan, moeten we in korte tijd 10.000 threads maken. Het maken van threads kost niet weinig, daarom heeft het een negatieve invloed op de prestaties van het programma en kan het zelfs een OOM-fout veroorzaken. Om threads te beheren, moeten we een threadpool implementeren en Java biedt de ExecutorService-klasse.
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 + "");
}
}
We hebben het aantal threads beperkt tot 10 en we kunnen de asynchrone verwerking op de juiste manier doen zoals we willen voor multithreading. Maar deze methode vereist dat we de submit()-methode van ExecutorService toepassen op elke methode die we asynchroon willen verwerken, wat betekent dat we herhaaldelijk moeten wijzigen. Dat wil zeggen, als we een methode die oorspronkelijk synchroon was geschreven, asynchroon willen maken, moeten we de logica van de methode zelf wijzigen.
Spring @Async
Een eenvoudige methode
@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 + "");
}
}
Plak de @EnableAsync-annotatie boven de Application-klasse en plak de @Async-annotatie boven de methode met de synchrone logica die we asynchroon willen verwerken. Dat is alles. Maar deze methode heeft het probleem dat threads niet worden beheerd. Dit komt omdat de standaardinstelling van @Async is om SimpleAsyncTaskExecutor te gebruiken, wat geen threadpool is, maar alleen threads maakt.
Een methode om een threadpool te gebruiken
Verwijder eerst @EnableAsync uit de Application-klasse. Als de Application-klasse @EnableAutoConfiguration of @SpringBootApplication heeft ingesteld, wordt de threadPoolTaskExecutor-bean-informatie in de SpringAsyncConfig-klasse (die we later gaan maken) tijdens runtime ingeladen omdat @Configuration is ingesteld.
@Configuration
@EnableAsync // moet boven de Async-configuratieklasse worden geplaatst, niet boven Application.
public class SpringAsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(3); // Aantal standaardthreads
taskExecutor.setMaxPoolSize(30); // Maximaal aantal threads
taskExecutor.setQueueCapacity(100); // Grootte van de wachtrij
taskExecutor.setThreadNamePrefix("Executor-");
return taskExecutor;
}
We kunnen de core- en max-grootte instellen. In dit geval zou u verwachten dat het met de initiële core-grootte werkt en dat het aantal threads wordt verhoogd tot de max-grootte als er geen taken meer kunnen worden verwerkt, maar dat is niet zo.
Intern maakt het een LinkedBlockingQueue met een grootte van Integer.MAX_VALUE aan, dus als de core-threads geen taken meer kunnen verwerken, wachten ze in de wachtrij. Als de wachtrij vol is, worden er threads gemaakt tot de max-grootte om de taken te verwerken.
Als u het niet prettig vindt om de grootte van de wachtrij in te stellen op Integer.MAX_VALUE, kunt u queueCapacity instellen. Als u het zo instelt, worden de taken eerst door 3 threads verwerkt. Als de verwerkingsnelheid lager is, worden de taken in de wachtrij geplaatst met een grootte van 100. Als er meer aanvragen binnenkomen, worden er maximaal 30 threads gemaakt om de taken te verwerken.
Nadat de threadpool is ingesteld, moet u de naam van de bean toevoegen in de methode die is geannoteerd met @Async.
@Service
public class MessageService {
@Async("threadPoolTaskExecutor")
public void print(String message) {
System.out.println(message);
}
Als u verschillende soorten threadpools wilt instellen, maakt u meerdere bean-creatiemethoden zoals threadPoolTaskExecutor() en voegt u de gewenste threadpool-bean in bij het instellen van @Async.
Het type retourwaarde per retourwaarde
Als er geen retourwaarde is
Dit is het geval wanneer de methode die asynchroon moet worden verwerkt, geen verwerkingsresultaten hoeft te leveren. In dit geval kunt u de retourwaarde van de @Async-annotatie instellen op void.
Als er een retourwaarde is
U kunt Future-, ListenableFuture- en CompletableFuture-typen gebruiken als retourtype. U kunt de retourvorm van de asynchrone methode inpakken in new AsyncResult().
[Toekomst]
@Service
public class MessageService {
@Async
public Future print(String message) throws InterruptedException {
System.out.println("Taak starten - " + 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());
}
}
}
// Uitvoerresultaat
Taak starten - 1
jayon-1
Taak starten - 2
jayon-2
Taak starten - 3
jayon-3
Taak starten - 4
jayon-4
Taak starten - 5
future.get() blokkeert en wacht tot het resultaat van de aanvraag is ontvangen. Dit maakt het een asynchroon blokkerend mechanisme, wat betekent dat het niet goed presteert. Future wordt meestal niet gebruikt.
[ListenableFuture]
@Service
public class MessageService {
@Async
public ListenableFuture print(String message) throws InterruptedException {
System.out.println("Taak starten - " + 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()));
}
}
}
// Uitvoerresultaat
Taak starten - 1
Taak starten - 3
Taak starten - 2
jayon-1
jayon-2
Taak starten - 5
jayon-3
Taak starten - 4
jayon-4
ListenableFuture maakt het mogelijk om taken asynchroon en zonder blokkades te verwerken via callbacks. De eerste parameter van de addCallback()-methode definieert de callback-methode voor taakvoltooiing en de tweede parameter definieert de callback-methode voor taakfouten. Ter informatie: aangezien de core-threads van de threadpool op 3 zijn ingesteld, kunt u zien dat de "Taak starten"-berichten aanvankelijk 3 keer worden weergegeven.
[CompletableFuture]
We kunnen niet-blokkerende logica implementeren met alleen ListenableFuture, maar als er een callback binnen een callback nodig is, wordt de code erg complex, wat wordt aangeduid als callback-hell.
Natuurlijk zullen we CompletableFuture deze keer niet in detail bespreken, dus als u nieuwsgierig bent naar code die complexe callbacks aanpakt, raadpleeg dan de link naar de bron in de onderstaande bron.
@Service
public class MessageService {
@Async
public CompletableFuture print(String message) throws InterruptedException {
System.out.println("Taak starten - " + 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;
});
}
}
Het heeft een betere leesbaarheid dan de callback-definitie van ListenableFuture en voert niet-blokkerende functionaliteit perfect uit. Daarom wordt het gebruik van CompletableFuture aanbevolen wanneer u @Async gebruikt en er een retourwaarde nodig is.
Voordelen van @Async
Ontwikkelaars kunnen methoden synchroon schrijven en vervolgens eenvoudig de @Async-annotatie boven de methode plaatsen als ze asynchrone functionaliteit willen. Dit maakt het mogelijk om code te maken die gemakkelijk te onderhouden is voor zowel synchronisatie als asynchrone methoden.
Voorzorgsmaatregelen bij @Async
Om de @Async-functionaliteit te gebruiken, moet u de @EnableAsync-annotatie declareren. Als u geen aparte instelling instelt, werkt deze in de proxymodus. Met andere woorden, alle asynchrone methoden die werken met de @Async-annotatie volgen de beperkingen van Spring AOP. Zie voor meer informatiedeze post.
- AOP werkt niet als u @Async aan een private methode plaatst.
- AOP werkt niet als methoden in hetzelfde object elkaar aanroepen.