Я хочу реализовать ExecutorService в моем приложении Spring-MVC.
Мне нужен глобальный ExecutorService, который принимает задачи, помещает их в очередь, а затем выполняет их последовательно. Поэтому я хочу передавать задачи из разных мест в приложении. Поэтому я использую Executors.newSingleThreadExecutor();
, поэтому у меня есть только 1 поток, выполняющий эти задачи.
Однако я не уверен, как интегрировать его в приложение Spring:
public enum TaskQueue {
INSTANCE;
ExecutorService executorService;
private TaskQueue() {
executorService = Executors.newSingleThreadExecutor();
}
public void addTaskToQueue (Runnable task){
executorService.submit(task);
executorService.shutdown();
}
}
Итак, я думаю о создании синглтона, а затем просто передать задачу (объект Runnable) методу:
TaskQueue.INSTANCE.addTaskToQueue(new Runnable() {
@Override
public void run() {
System.out.println("Executing Task1 inside : " + Thread.currentThread().getName());
}
});
Однако у меня есть несколько вопросов:
Я не уверен, как интегрировать его в приложение Spring MVC, где у меня есть контроллеры, службы и так далее.
Приложение получает некоторые уведомления от веб-службы. Эти уведомления будут обрабатываться в разных местах кода. Я хочу выполнять их последовательно. Поэтому мне нужно определить все задачи, которые я хочу запускать асинхронно, а затем передать их указанному выше методу (`addTaskToQueue), заключенному в объект Runnabel, чтобы их можно было выполнять асинхронно. Это правильный подход?
Поэтому я всегда передаю объект Runnable этому методу для его выполнения. Этот метод выполняет его и завершает работу службы исполнителя. Поэтому каждый раз, когда я передаю задачу этой службе, она создает новую службу, а затем закрывает ее. Но я не хочу этого - я хочу, чтобы служба исполнителя оставалась в живых и выполняла задачи, которые поступают, а не завершалась после каждой задачи. Как мне этого добиться?
Или я совершенно не прав, реализуя это таким образом?
Обновлено:
Используя TaskExecutor, предоставляемый Spring, я бы реализовал его так:
@Autowired
private TaskExecutor taskExecutor;
Затем вызывая его из другого места в моем коде:
taskExecutor.execute(new Runnable() {
@Override
public void run() {
//TODO add long running task
}
});
Вероятно, мне нужно где-то в конфигурации убедиться, что он должен выполняться последовательно, поэтому он должен создавать только 1 поток. Насколько безопасно звонить из разных мест? Он не всегда будет создавать новую услугу с новой очередью?
@SeverityOne Но когда мне нужно его выключить? Я хочу передавать задачи из нескольких мест в приложении Spring, и я не знаю, где поставить завершение работы.
Когда он вам больше не нужен. Я лучше напишу на это ответ.
Моя самая большая проблема - это интеграция всей концепции в приложение Spring MVC. У меня есть метод веб-службы, который получает все уведомления, а затем вызываются другие службы для выполнения некоторых задач. Может быть, мне нужно поставить его в конце метода веб-службы?
@ Norbert94 вы устанавливаете maxNumber
потоков при настройке bean-компонента taskExecutor
в XML или в конфигурации Java. По умолчанию bean-компоненты Spring являются одиночными, поэтому для каждого контекста приложения будет создан только один экземпляр taskExecutor
.
@Ivan Да, извините, это был глупый вопрос. Конечно, у Spring beans есть только один экземпляр. Согласны ли вы с вызовом этого метода в разных службах в моем приложении Spring? Возможно, мне не хватило бы памяти, если бы в очереди было много задач
@ Norbert94, если вы боитесь получить OOM в случае добавления большого количества задач в очередь, вы можете создать taskExecutor
с более чем одним потоком. Также, если у вас разные задачи (один набор задач является длительным, а другой набор задач быстро выполняется), вам лучше иметь два разных bean-компонента taskExecutor
для этих двух типов задач. Но в любом случае можно использовать этот бин из нескольких других бобов.
В Spring уже есть компонент для этого: org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
. Он реализует интерфейс DisposableBean
, и метод shutdown()
вызывается при уничтожении контекста Spring.
<bean id = "taskExecutor" class = "org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name = "corePoolSize" value = "${threadPoolSize}"/>
<property name = "maxPoolSize" value = "${threadPoolSize}"/>
<property name = "WaitForTasksToCompleteOnShutdown" value = "false"/>
</bean>
Спасибо за ответ - я отредактировал свой вопрос и предоставил образец кода для этого подхода - я был бы признателен, если бы вы могли его проверить.
Шаблон проектирования Singleton не очень подходит для этого. Поскольку вы используете Spring, имеет смысл создавать компонент с методами @PostConstruct
и @PreDestroy
. Здесь и здесь - статьи, объясняющие это.
В вашем случае я бы сделал что-то вроде следующего:
@Component
public class TaskQueue {
private ExecutorService executorService;
@PostConstruct
public void init() {
executorService = Executors.newSingleThreadExecutor();
}
@PreDestroy
public void cleanup() {
executorService.shutdown();
}
public void addTaskToQueue (Runnable task){
executorService.submit(task);
}
}
И затем вы можете автоматически подключить этот компонент.
Обновлено: @ Иван ответ предпочтительнее.
Почему вы выключаете
ExecutorService
сразу после добавления к нему задачи?