Какие основные способы организации многопоточности в Spring-приложении?

«Какие основные способы организации многопоточности в Spring-приложении?» — вопрос из категории Spring, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Spring предоставляет несколько высокоуровневых абстракций для работы с многопоточностью, избавляя от необходимости напрямую управлять Thread.

1. Асинхронное выполнение через @Async:

  • Требует аннотации @EnableAsync в конфигурации.
  • Метод, помеченный @Async, выполняется в отдельном потоке.
  • Возвращаемый тип должен быть void, Future или CompletableFuture.
    @Service
    public class NotificationService {
    @Async // Будет выполнен в потоке из TaskExecutor
    public CompletableFuture<String> sendAsync() {
        // Долгая операция (отправка email, запрос к API)
        return CompletableFuture.completedFuture("OK");
    }
    }

2. Настройка пула потоков (TaskExecutor):

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);     // Основные потоки
        executor.setMaxPoolSize(10);     // Максимум при заполненной очереди
        executor.setQueueCapacity(25);   // Очередь задач
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }
}

3. Планирование задач с @Scheduled:

  • Требует @EnableScheduling.
  • Для периодического или отложенного выполнения.
    @Component
    public class ScheduledTasks {
    @Scheduled(fixedRate = 5000) // Каждые 5 секунд
    public void report() {
        // Логика задачи
    }
    }

4. Реактивное программирование (Spring WebFlux):

  • Альтернативная модель для асинхронной неблокирующей обработки с использованием Project Reactor (Mono, Flux).

Важные аспекты:

  • Потокобезопасность: Бины по умолчанию являются синглтонами. Убедитесь, что их состояние либо неизменяемо, либо синхронизировано.
  • Контекст: В асинхронных потоках теряется контекст Security, транзакций и др. Его нужно передавать явно.
  • Обработка исключений: Для @Async с Future исключения оборачиваются в ExecutionException. Рекомендуется использовать AsyncUncaughtExceptionHandler.