Przetwarzanie asynchroniczne w Javie
Zanim przyjrzymy się Spring @Async, konieczne jest zrozumienie pojęć synchronizacji, asynchroniczności i wielowątkowości. Zakładamy, że znasz te pojęcia i przyjrzymy się sposobom przetwarzania asynchronicznego w czystej Javie za pomocą kodu. Jeśli dobrze znasz wątki Java, możesz pominąć ten rozdział.
Jeśli stworzymy funkcję, która po prostu odbiera wiadomość i ją wyświetla, w sposób synchroniczny, to kod będzie wyglądał jak powyżej. Zmieniając to na wielowątkową wersję asynchroniczną, możemy napisać kod źródłowy w następujący sposób.
Jednak ta metoda jest bardzo niebezpieczna, ponieważ nie pozwala na zarządzanie wątkami. Na przykład, jeśli jednocześnie zostanie wykonanych 10 000 wywołań, w bardzo krótkim czasie trzeba będzie utworzyć 10 000 wątków. Koszt tworzenia wątków nie jest mały, więc może negatywnie wpłynąć na wydajność programu, a nawet spowodować błąd OOM. Dlatego, aby zarządzać wątkami, należy zaimplementować pulę wątków, a Java oferuje klasę ExecutorService.
Ograniczamy całkowitą liczbę wątków do 10 i możemy poprawnie wykonać żądaną asynchroniczną obróbkę w trybie wielowątkowym. Jednak w powyższej metodzie dla każdej metody, którą chcemy obsłużyć asynchronicznie, musimy zastosować metodę submit() klasy ExecutorService, co wymaga powtarzalnych modyfikacji. Oznacza to, że jeśli na początku chcemy zmienić metodę zapisaną w logice synchronicznej na asynchroniczną, nie można uniknąć zmiany samej logiki metody.
Spring @Async
Prosty sposób
Wystarczy dołączyć adnotację @EnableAsync nad klasą Application i adnotację @Async nad metodą synchroniczną, którą chcemy obsłużyć asynchronicznie. Jednak ten sposób ma problem z brakiem zarządzania wątkami. Dzieje się tak, ponieważ domyślne ustawienie @Async używa SimpleAsyncTaskExecutor, który nie jest pulą wątków, a jedynie tworzy wątki.
Sposób użycia puli wątków
Najpierw usuwamy @EnableAsync z klasy Application. Jeśli w klasie Application jest ustawienie @EnableAutoConfiguration lub @SpringBootApplication, w czasie wykonywania odczytuje informacje o fasoli threadPoolTaskExecutor z klasy SpringAsyncConfig (utworzonej poniżej), która jest skonfigurowana jako @Configuration.
Możemy ustawić rozmiary core i max. W tym momencie możemy oczekiwać, że początkowo będzie działać tyle wątków, ile wynosi rozmiar core, a jeśli nie będzie można obsłużyć większej liczby zadań, liczba wątków wzrośnie do rozmiaru max. Jednak tak się nie dzieje.
Wewnątrz tworzona jest kolejka LinkedBlockingQueue o rozmiarze Integer.MAX_VALUE, więc jeśli core wątków nie może obsłużyć zadania, czeka ono w kolejce. Dopiero gdy kolejka jest pełna, tworzonych jest max wątków w celu obsługi zadań.
Jeśli ustawienie rozmiaru kolejki na Integer.MAX_VALUE jest zbyt uciążliwe, można ustawić queueCapacity. Jeśli ustawimy to tak, jak powyżej, to początkowo 3 wątki będą obsługiwać zadania, a jeśli szybkość obsługi zadań spadnie, zadania będą umieszczane w kolejce o rozmiarze 100, a jeśli nadejdą dalsze żądania, zostanie utworzonych maksymalnie 30 wątków w celu obsługi zadań.
Po skonfigurowaniu puli wątków wystarczy podać nazwę tej fasoli w metodzie z adnotacją @Async.
Jeśli chcesz ustawić kilka rodzajów puli wątków, możesz utworzyć kilka metod tworzenia fasoli, takich jak threadPoolTaskExecutor(), a podczas ustawiania @Async możesz wstawić żądaną fasolę puli wątków.
Forma zwracana w zależności od typu zwracanego
Brak wartości zwracanej
Jest to przypadek, gdy metoda, którą należy przetworzyć asynchronicznie, nie musi przekazywać wyniku przetwarzania. W takim przypadku typem zwracanym adnotacji @Async jest void.
Istnieje wartość zwracana
Można użyć typów Future, ListenableFuture i CompletableFuture jako typów zwracanych. Formę zwracaną metody asynchronicznej można zamknąć w new AsyncResult().
[Future]
future.get() blokuje i czeka na wynik żądania. Dlatego staje się to synchroniczną blokującą metodą, co nie jest dobre dla wydajności. Zazwyczaj Future nie jest używane.
[ListenableFuture]
ListenableFuture umożliwia przetwarzanie zadań w sposób nieblokujący za pomocą mechanizmu zwrotnego. W metodzie addCallback() pierwszym parametrem jest metoda zwrotnego wywołania po zakończeniu zadania, a drugim parametrem jest metoda zwrotnego wywołania w przypadku błędu zadania. Należy zauważyć, że ponieważ liczba rdzeni puli wątków jest ustawiona na 3, można zauważyć, że wiadomość „Task Start” jest wyświetlana 3 razy na początku.
[CompletableFuture]
Chociaż ListenableFuture pozwala na zaimplementowanie logiki nieblokującej, jeśli konieczne jest zagnieżdżenie zwrotnego wywołania w zwrotnym wywołaniu, może to doprowadzić do bardzo skomplikowanego kodu, zwanego „piekłem zwrotnego wywołania”.
<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>
Oczywiście w tym przypadku nie będziemy szczegółowo omawiać CompletableFuture, więc jeśli jesteś zainteresowany kodem radzenia sobie ze złożonymi zwrotnymi wywołaniami, zapoznaj się z linkiem w poniższym źródle.
Definicja zwrotnego wywołania w ListenableFuture jest łatwiejsza do odczytania, a funkcja nieblokująca działa w pełni. Dlatego zaleca się używanie CompletableFuture, gdy używasz @Async i potrzebujesz wartości zwracanej.
Zalety @Async
Deweloperzy mogą pisać metody w sposób synchroniczny, a jeśli chcą, aby działały one asynchronicznie, wystarczy dodać adnotację @Async nad metodą. Dzięki temu można tworzyć kod, który jest łatwy w utrzymaniu zarówno w przypadku synchronizacji, jak i asynchroniczności.
Uwagi dotyczące @Async
Aby użyć funkcji @Async, należy zadeklarować adnotację @EnableAsync, ale jeśli nie zostanie dokonana żadna dodatkowa konfiguracja, będzie ona działać w trybie proxy. Oznacza to, że wszystkie metody asynchroniczne działające z adnotacją @Async będą podlegać ograniczeniom Spring AOP. Aby uzyskać więcej informacji, zapoznaj się z tym wpisem.
- Jeśli adnotacja @Async zostanie dołączona do metody prywatnej, AOP nie zadziała.
- Jeśli metody w tym samym obiekcie wywołują się wzajemnie, AOP nie zadziała.
Komentarze0