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

Это сообщение переведено AI.

제이온

[Spring] Использование @Async

  • Язык написания: Корейский
  • Базовая страна: Все страны country-flag

Выбрать язык

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

Текст, резюмированный ИИ durumis

  • В этой статье рассматривается, как реализовать асинхронную обработку Java с помощью Spring @Async, а также ее преимущества и нюансы.
  • Используя Spring @Async, вы можете легко реализовать асинхронную обработку, добавив аннотацию @EnableAsync и добавив аннотацию @Async к методам, которые вы хотите сделать асинхронными.
  • Настройка пула потоков позволяет эффективно управлять потоками, а в зависимости от типа возвращаемого значения можно использовать Future, ListenableFuture, CompletableFuture и т. д. для возврата результатов асинхронной обработки.

Обработка Java в асинхронном режиме

Прежде чем изучать Spring @Async, необходимо понять концепции синхронности, асинхронности и многопоточности. Предполагая, что вы знакомы с этими концепциями, давайте рассмотрим, как выполнить асинхронную обработку в чистом Java. Если вы знакомы с потоками Java, вы можете пропустить эту главу.


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


Если вы хотите синхронно реализовать функцию, которая получает сообщение и просто выводит его, вы можете написать код, как показано выше. Изменив этот код на многопоточный асинхронный вариант, вы получите следующий код:


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


Однако этот метод очень опасен, так как вы не можете управлять потоками. Например, если одновременно выполняется 10 000 вызовов, вам нужно создать 10 000 потоков за очень короткое время. Стоимость создания потока не маленькая, поэтому это негативно сказывается на производительности программы, и даже может привести к ошибке OOM. Поэтому, для управления потоками необходимо реализовать пул потоков, и Java предоставляет для этого класс 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 + "");
        }
    }


Мы ограничили общее количество потоков 10 и правильно реализовали многопоточную асинхронную обработку. Однако, в этом методе, вам нужно использовать метод submit() ExecutorService для каждого метода, который вы хотите обработать асинхронно, что означает, что вам нужно выполнить повторяющуюся корректировку. То есть, если вы хотите изменить метод, написанный изначально синхронно, на асинхронный, вам неизбежно придется изменять сам код метода.


Spring @Async

Простой метод

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


Просто добавьте аннотацию @EnableAsync в класс Application и аннотацию @Async в метод, который вы хотите обработать асинхронно. Однако, этот метод не позволяет управлять потоками. Поскольку @Async по умолчанию использует SimpleAsyncTaskExecutor, который не является пулом потоков, а просто создает потоки.


Использование пула потоков

Сначала удалите @EnableAsync из класса Application. Если в классе Application есть настройки @EnableAutoConfiguration или @SpringBootApplication, то во время выполнения будет считываться информация о bean threadPoolTaskExecutor (которую мы создадим позже) из класса SpringAsyncConfig, в котором настроена @Configuration.


@Configuration
@EnableAsync // Должно быть в классе Async-конфигурации, а не в Application.
public class SpringAsyncConfig {

    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(3); // Количество основных потоков
        taskExecutor.setMaxPoolSize(30); // Максимальное количество потоков
        taskExecutor.setQueueCapacity(100); // Размер очереди
        taskExecutor.setThreadNamePrefix("Executor-");
        return taskExecutor;
    }


Вы можете настроить размер ядра и максимальный размер. В этом случае, вы можете предположить, что он работает с размером ядра, а затем, если он не может обрабатывать больше работы, размер потока увеличится до максимального размера, но это не так.


Внутренне он создает LinkedBlockingQueue размером Integer.MAX_VALUE, и если задача не может быть обработана 3 потоками ядра, то она будет ждать в очереди. Когда очередь заполнится, то для обработки задач будут создаваться потоки до максимального размера.


Если вы не хотите, чтобы размер очереди был Integer.MAX_VALUE, вы можете настроить queueCapacity. Настроив его как показано выше, сначала будет обрабатываться 3 потока ядра, если скорость обработки будет снижаться, задачи будут помещаться в очередь размером 100, и если поступает больше запросов, будет создаваться до 30 потоков для обработки задач.


Настройка пула потоков завершена, теперь вам нужно просто добавить имя bean к методу с аннотацией @Async.


@Service
public class MessageService {

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


Если вы хотите настроить несколько типов пулов потоков, вы можете создать несколько методов создания bean, таких как threadPoolTaskExecutor(), и указать нужный bean в настройках @Async.


Типы возвращаемых значений

Без возвращаемого значения

Это случай, когда метод, который нужно обработать асинхронно, не должен возвращать никаких результатов. В этом случае, в качестве типа возвращаемого значения @Async, вы должны использовать void.


С возвращаемым значением

В качестве типа возвращаемого значения можно использовать типы Future, ListenableFuture, CompletableFuture. Чтобы вернуть результат асинхронного метода, нужно обернуть его в 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());
        }
    }
}

// Результат выполнения
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() блокирует и ждет, пока не придет результат запроса. Поэтому это асинхронный блокирующий метод, который не дает хорошей производительности. Обычно Future не используется.


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

// Результат выполнения
Task Start - 1
Task Start - 3
Task Start - 2
jayon-1
jayon-2
Task Start - 5
jayon-3
Task Start - 4
jayon-4


ListenableFuture позволяет обрабатывать задачи в неблокирующем режиме с помощью обратного вызова. Первый параметр метода addCallback() — это метод обратного вызова при успешном завершении задачи, второй параметр — метод обратного вызова при ошибке. Обратите внимание, что поскольку размер потока ядра установлен в 3, вы можете видеть, что сообщение "Task Start" выводится 3 раза в начале.



[CompletableFuture]

ListenableFuture позволяет реализовать неблокирующую логику, но если вам нужно встроить обратный вызов в обратный вызов, то это может привести к очень сложному коду, который называется "ад обратных вызовов".


Untitled


Конечно, в этот раз мы не будем подробно рассматривать CompletableFuture, поэтому, если вам интересно, как справиться со сложными обратными вызовами, обратитесь к ссылке, указанной ниже.


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


Читаемость лучше, чем определение обратного вызова для ListenableFuture, и он полностью реализует функции неблокирования. Поэтому, если вам нужно вернуть значение при использовании @Async, рекомендуется использовать CompletableFuture.


Преимущества @Async

Разработчику нужно просто добавить аннотацию @Async в метод, если он хочет использовать асинхронный метод, написанный синхронно. Это позволяет создавать код с хорошим обслуживанием синхронного и асинхронного режима.


Что нужно помнить при использовании @Async

Для использования функции @Async необходимо объявить аннотацию @EnableAsync, но если не использовать никаких дополнительных настроек, она будет работать в режиме прокси. Это означает, что все асинхронные методы, которые используют аннотацию @Async, должны соблюдать все ограничения Spring AOP. Подробнее см. в этом сообщении.


  • Если добавить @Async к частному методу, то AOP работать не будет.
  • AOP не будет работать при вызове методов из одного и того же объекта.


Источники

제이온
제이온
제이온
제이온
[Java] Synchronized Collection vs Concurrent Collection В этой статье мы сравним и проанализируем различные подходы к решению проблем синхронизации при использовании коллекций в многопоточной среде в Java. Мы рассмотрим особенности и разницу в производительности синхронизированных коллекций, таких как Vector,

25 апреля 2024 г.

[Spring] Что такое Filter, Interceptor, Argument Resolver? Узнайте больше о концепциях и различиях Filter, Interceptor, Argument Resolver в веб-приложениях Spring. Изучите способы реализации каждой функции, время использования, преимущества и недостатки, а также получите понимание с помощью реальных примеров кода

27 апреля 2024 г.

[Эффективная Java] Элемент 6. Избегайте ненужного создания объектов Руководство по минимизации ненужного создания объектов в Java. Для неизменяемых объектов, таких как String, Boolean, рекомендуется использовать литералы, а для регулярных выражений – кэшировать экземпляры Pattern. Кроме того, автоупаковка может привести к

28 апреля 2024 г.

[Конкурентность] Атомарные операции: Ограждения памяти и порядок памяти В этом посте блога мы рассмотрим, как учитывать порядок памяти в атомарных операциях, и важность опций упорядочения. Мы подробно рассмотрим различные опции упорядочения, такие как Relaxed, Acquire, Release, AcqRel, SecCst, а также их преимущества и недост
곽경직
곽경직
곽경직
곽경직
곽경직

12 апреля 2024 г.

Как Rust предотвращает ошибки в многопоточности Rust — это мощный язык, который решает проблемы с многопоточной программированием. Благодаря системе типов и модели владения, передача и общий доступ к данным между потоками безопасны. С помощью таких паттернов внутренней изменчивости, как Mutex, Channel
곽경직
곽경직
곽경직
곽경직
곽경직

28 марта 2024 г.

[Для неспециалистов, выживание как разработчик] 14. Краткое изложение часто задаваемых вопросов на техническом собеседовании для начинающих разработчиков Руководство по подготовке к техническому собеседованию для начинающих разработчиков. Объясняются концепции, которые часто встречаются на собеседованиях, такие как область основной памяти, структуры данных, RDBMS и NoSQL, процедурное и объектно-ориентирова
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

3 апреля 2024 г.

[Нетехнический специалист, выживание в качестве разработчика] 16. Советы по составлению портфолио для начинающих разработчиков Начинающим разработчикам (особенно нетехническим специалистам) при составлении портфолио необходимо четко описывать не только технологии, но и разработанные сервисы или функции. Например, если проект — это "Сообщество для ищущих работу", в него следует вк
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

3 апреля 2024 г.

Физическое моделирование данных Физическое моделирование данных — это процесс проектирования таблиц реляционной базы данных для практического использования, ориентированный на оптимизацию производительности за счет эффективности использования дискового пространства, разбиения данных, пр
제이의 블로그
제이의 블로그
제이의 블로그
제이의 블로그
제이의 블로그

9 апреля 2024 г.

Полный стек ИИ на основе открытого кода В экосистеме ИИ появляются все новые модели ИИ с открытым исходным кодом (LLM). Mistral, Llama, phi-2 и другие модели с мощными возможностями и открытыми лицензиями были опубликованы, а также разрабатываются различные инструменты для их использования. Lan
RevFactory
RevFactory
RevFactory
RevFactory

5 февраля 2024 г.