Try using it in your preferred language.

English

  • English
  • 汉语
  • Español
  • Bahasa Indonesia
  • Português
  • Русский
  • 日本語
  • 한국어
  • Deutsch
  • Français
  • Italiano
  • Türkçe
  • Tiếng Việt
  • ไทย
  • Polski
  • Nederlands
  • हिन्दी
  • Magyar
translation

To jest post przetłumaczony przez AI.

제이온

[Spring] Sposób użycia @Async

  • Język pisania: Koreański
  • Kraj referencyjny: Wszystkie kraje country-flag

Wybierz język

  • Polski
  • English
  • 汉语
  • Español
  • Bahasa Indonesia
  • Português
  • Русский
  • 日本語
  • 한국어
  • Deutsch
  • Français
  • Italiano
  • Türkçe
  • Tiếng Việt
  • ไทย
  • Nederlands
  • हिन्दी
  • Magyar

Tekst podsumowany przez sztuczną inteligencję durumis

  • Opisuje sposób implementacji asynchronicznego przetwarzania Java przy użyciu Spring @Async, jego zalety i kwestie, na które należy zwrócić uwagę.
  • Używając Spring @Async, możesz w prosty sposób zaimplementować asynchroniczne przetwarzanie, dodając adnotację @EnableAsync i dodając adnotację @Async do metod, które mają być przetwarzane asynchronicznie.
  • Konfiguracja puli wątków umożliwia wydajne zarządzanie wątkami, a typ zwracany pozwala na użycie Future, ListenableFuture, CompletableFuture itp. do zwracania wyników asynchronicznego przetwarzania.

Przetwarzanie asynchroniczne w Javie

Zanim przyjrzymy się Spring @Async, konieczne jest poznanie koncepcji synchronizacji, asynchroniczności i wielowątkowości. Zakładając, że znasz te koncepcje, przyjrzyjmy się kodowi implementującemu asynchroniczne przetwarzanie w czystej Javie. Jeśli jesteś zaznajomiony z wątkami w Javie, możesz pominąć ten rozdział.


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 + "");
        }
    }


Jeśli utworzymy funkcję, która po prostu odbiera wiadomość i ją wypisuje, możemy napisać kod w sposób synchroniczny, jak pokazano powyżej. Zamieniając to na asynchroniczny sposób przetwarzania z użyciem wielowątkowości, możemy napisać kod źródłowy w następujący sposób.


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 + "");
        }
    }


Jednak ta metoda jest bardzo niebezpieczna, ponieważ nie pozwala na zarządzanie wątkami. Na przykład, jeśli jednocześnie zostanie wywołanych 10 000 wywołań, trzeba będzie bardzo szybko utworzyć 10 000 wątków. Tworzenie wątków jest kosztowne, co może negatywnie wpłynąć na wydajność programu, a nawet spowodować błąd OOM. Dlatego do zarządzania wątkami należy zaimplementować pulę wątków. Java oferuje klasę 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 + "");
        }
    }


Ograniczyliśmy całkowitą liczbę wątków do 10, a także możemy prawidłowo przeprowadzić asynchroniczne przetwarzanie wielowątkowe, które chcemy. Jednak powyższa metoda wymaga zastosowania metody submit() klasy ExecutorService do każdej metody, którą chcemy przetworzyć asynchronicznie, co wymaga powtarzalnych modyfikacji. Oznacza to, że jeśli chcemy zamienić metodę pierwotnie zapisaną w sposób synchroniczny na asynchroniczny, musimy zmienić samą logikę metody.


Spring @Async

Prosty sposób

@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 + "");
        }
    }


Wystarczy dołączyć adnotację @EnableAsync nad klasą aplikacji, a następnie adnotację @Async nad metodą, której logikę chcemy przetworzyć asynchronicznie. Jednak powyższa metoda nie zarządza wątkami. Ponieważ domyślnym ustawieniem @Async jest użycie SimpleAsyncTaskExecutor, który nie jest pulą wątków, a jedynie tworzy wątki.


Metoda korzystająca z puli wątków

Najpierw usuń @EnableAsync z klasy aplikacji. Jeśli klasa aplikacji ma ustawienie @EnableAutoConfiguration lub @SpringBootApplication, w czasie wykonywania odczyta informacje o beanach threadPoolTaskExecutor z klasy SpringAsyncConfig (która zostanie utworzona poniżej), która ma ustawione @Configuration.


@Configuration
@EnableAsync // Powinna być dołączona do klasy konfiguracji asynchronicznej, a nie do aplikacji.
public class SpringAsyncConfig {

    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(3); // Liczba wątków podstawowych
        taskExecutor.setMaxPoolSize(30); // Maksymalna liczba wątków
        taskExecutor.setQueueCapacity(100); // Rozmiar kolejki
        taskExecutor.setThreadNamePrefix("Executor-");
        return taskExecutor;
    }


Możesz ustawić rozmiary rdzenia i maks. Możesz oczekiwać, że będzie działać z liczbą rdzeni, a następnie zwiększać liczbę wątków do rozmiaru maks., jeśli nie będzie możliwe przetworzenie dalszych zadań, ale tak nie jest.


Wewnętrznie tworzy kolejkę LinkedBlockingQueue o rozmiarze Integer.MAX_VALUE, więc jeśli wątki rdzenia nie będą w stanie przetworzyć zadań, zaczną czekać w kolejce. Gdy kolejka zostanie zapełniona, utworzone zostaną wątki o rozmiarze maks., aby przetworzyć zadania.


Jeśli ustawienie rozmiaru kolejki na Integer.MAX_VALUE jest zbyt obciążające, możesz ustawić queueCapacity. W powyższym przypadku, zaczynając od 3 wątków, będzie przetwarzać, a jeśli prędkość przetwarzania będzie spadać, będzie przechowywać zadania w kolejce o rozmiarze 100, a jeśli będzie więcej żądań, utworzy maksymalnie 30 wątków, aby przetworzyć zadania.


Po skonfigurowaniu puli wątków należy dodać nazwę tego beana do metody z adnotacją @Async.


@Service
public class MessageService {

    @Async("threadPoolTaskExecutor")
    public void print(String message) {
        System.out.println(message);
    }


Jeśli chcesz ustawić wiele typów puli wątków, utwórz wiele metod tworzenia beanów, takich jak threadPoolTaskExecutor(), a następnie dodaj żądany bean puli wątków podczas ustawiania @Async.


Typ zwracany przez różne typy zwrotne

Gdy nie ma wartości zwrotnej

Jest to przypadek, gdy metoda, którą należy przetworzyć asynchronicznie, nie musi przekazywać wyniku. W tym przypadku należy ustawić typ zwracany adnotacji @Async na void.


Gdy jest wartość zwrotna

Jako typ zwracany można użyć typów Future, ListenableFuture i CompletableFuture. W przypadku asynchronicznej metody można zamknąć formę zwrotną w 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());
        }
    }
}

// Wynik wykonania
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() blokuje i czeka, aż wynik żądania zostanie zwrócony. W rezultacie staje się to asynchronicznym sposobem blokowania, co wpływa na wydajność. Zazwyczaj Future nie jest używane.


[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()));
        }
    }
}

// Wynik wykonania
Task Start - 1
Task Start - 3
Task Start - 2
jayon-1
jayon-2
Task Start - 5
jayon-3
Task Start - 4
jayon-4


ListenableFuture umożliwia przetwarzanie zadań w sposób nieblokujący za pomocą wywołania zwrotnego. Pierwszy parametr metody addCallback() to metoda wywołania zwrotnego po zakończeniu zadania, a drugi to metoda wywołania zwrotnego po błędzie. Należy zauważyć, że ponieważ rozmiar rdzenia puli wątków jest ustawiony na 3, można zauważyć, że komunikat „Task Start” jest wyświetlany początkowo 3 razy.



[CompletableFuture]

ListenableFuture umożliwia implementację logiki nieblokującej, ale jeśli potrzebne są wywołania zwrotne w wywołaniach zwrotnych, może to prowadzić do bardzo złożonego kodu, nazywanego „piekłem wywołań zwrotnych”.


Untitled


Oczywiście w tym przypadku nie będziemy szczegółowo omawiać CompletableFuture, więc jeśli jesteś ciekawy kodu, który radzi sobie ze złożonymi wywołaniami zwrotnymi, zajrzyj do linku w źródle poniżej.


@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;
                    });
        }
    }


Jest bardziej czytelny niż definiowanie wywołań zwrotnych w ListenableFuture i w pełni obsługuje funkcje nieblokujące. Dlatego zaleca się użycie CompletableFuture podczas korzystania z @Async, jeśli potrzebna jest wartość zwrotna.


Zalety @Async

Deweloperzy mogą pisać metody w sposób synchroniczny, a następnie dodawać adnotację @Async nad metodą, jeśli chcą użyć sposobu asynchronicznego. Dzięki temu można tworzyć kod, który jest łatwy w utrzymaniu i działa synchronicznie i asynchronicznie.


Punkty do zapamiętania dotyczące @Async

Aby korzystać z funkcji @Async, należy zadeklarować adnotację @EnableAsync, ale jeśli nie zostanie dokonana żadna dodatkowa konfiguracja, będzie działać w trybie proxy. Oznacza to, że wszystkie metody asynchroniczne, które działają z adnotacją @Async, będą podlegać ograniczeniom Spring AOP. Aby uzyskać więcej informacji, zapoznaj się ztym wpisem.


  • Nawet jeśli adnotację @Async zostanie dodana do metody prywatnej, AOP nie zadziała.
  • AOP nie zadziała podczas wywoływania metody przez tę samą instancję.


Źródła

제이온
제이온
제이온
제이온
[Java] Synchronized Collection vs Concurrent Collection W tym artykule porównano i przeanalizowano różne metody rozwiązywania problemów synchronizacji w środowiskach wielowątkowych w języku Java, a także ich wady i zalety. Przedstawiono cechy i różnice w wydajności między zsynchronizowanymi kolekcjami, takimi

25 kwietnia 2024

[Spring] Czym są filtry, interseptory i rozdzielacze argumentów? Dowiedz się więcej o filtrach, interseptach i rozdzielaczach argumentów w kontekście aplikacji webowych Springa, w tym o ich definicjach i różnicach. Porównaj implementacje, momenty użycia, zalety i wady tych funkcji, a także poznaj praktyczne przykłady k

27 kwietnia 2024

[Efektywny Java] Punkt 6. Unikaj niepotrzebnego tworzenia obiektów Przewodnik po sposobach zmniejszenia liczby niepotrzebnych tworzeń obiektów w Javie. W przypadku obiektów niezmiennych, takich jak String, Boolean, lepiej jest używać literałów, a wyrażenia regularne najlepiej buforować w instancji Pattern. Ponadto automa

28 kwietnia 2024

Jak Rust zapobiega błędom współbieżności Rust to potężny język, który rozwiązuje wyzwania związane z programowaniem współbieżnym. Jego system typów i model własności zapewniają bezpieczeństwo podczas przekazywania i udostępniania danych między wątkami. Wzory zmienności wewnętrznej, takie jak Mut
곽경직
곽경직
곽경직
곽경직
곽경직

28 marca 2024

[Współbieżność] Operacja atomowa: Płot pamięci i porządkowanie pamięci Ten wpis na blogu wyjaśnia, jak wziąć pod uwagę kolejność pamięci w operacjach atomowych oraz znaczenie opcji porządkowania. Zawiera szczegółowe wyjaśnienie różnych opcji porządkowania, takich jak Relaxed, Acquire, Release, AcqRel, SecCst, wraz z omówieni
곽경직
곽경직
곽경직
곽경직
곽경직

12 kwietnia 2024

[Bez stopnia, przetrwać jako programista] 14. Podsumowanie często zadawanych pytań na rozmowach kwalifikacyjnych dla początkujących programistów Przewodnik po przygotowaniu do rozmów kwalifikacyjnych dla programistów. Wyjaśnia takie pojęcia często pojawiające się podczas rozmów jak: obszary pamięci głównej, struktury danych, RDBMS i NoSQL, programowanie proceduralne i obiektowe, nadpisywanie i prz
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

3 kwietnia 2024

Czym jest wypełnianie slotów (Slot-Filling)? Wypełnianie slotów to proces, w którym chatbot zadaje pytania wielokrotnie, aż uzyska od użytkownika wszystkie niezbędne informacje. Na przykład podczas zamawiania kawy chatbot zapyta o rodzaj, temperaturę i wielkość, a zamówienie zostanie złożone dopiero
꿈많은청년들
꿈많은청년들
Obraz z dużym napisem "Wypełnianie slotów"
꿈많은청년들
꿈많은청년들

13 maja 2024

Pomysły na ulepszenie programu handlu automatycznego Prezentuję pomysły na ulepszenie funkcji programu automatycznego handlu siatkowego, w tym zarządzanie dużymi wydarzeniami, logikę zarządzania kapitałem, dodanie funkcji sprzedaży krótkiej. W szczególności wyjaśniam, że funkcja holdingu pozwala na lepszy m
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마

21 kwietnia 2024

Podstawowa struktura automatycznego handlu akcjami (w trakcie aktualizacji...) Ten artykuł szczegółowo opisuje proces tworzenia programu do automatycznego handlu akcjami krok po kroku, od otwarcia konta i przygotowania środowiska programistycznego, przez integrację API i projektowanie interfejsu użytkownika, po implementację kluczow
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마
(로또 사는 아빠) 살림 하는 엄마

22 kwietnia 2024