Spring Framework предусматривает абстракции для асинхронного выполнения и планирования задач с помощью интерфейсов TaskExecutor
и TaskScheduler
, соответственно. Spring также имеет реализации этих интерфейсов, которые поддерживают пулы потоков или делегирование полномочий CommonJ в серверном окружении приложений. В конечном итоге, использование этих реализаций за общими интерфейсами абстрагирует различия между окружениями Java SE 5, Java SE 6 и Java EE.
Spring также имеет классы интеграции для поддержки планирования с помощью Timer
(входит в состав JDK с версии 1.3) и Quartz Scheduler (https://www.quartz-scheduler.org/). Можно установить оба этих планировщика, используя FactoryBean
с дополнительными ссылками на экземпляры Timer
или Trigger
, соответственно. Кроме того, для планировщика Quartz и Timer
доступен вспомогательный класс, позволяющий вызывать метод существующего целевого объекта (аналогично обычной операции MethodInvokingFactoryBean
).
Абстракция TaskExecutor
в Spring
Исполнители (executors) – это JDK-наименование понятия пулов потоков. Именование "исполнитель" связано с тем, что отсутствует гарантия, что базовая реализация на самом деле является пулом. Исполнитель может быть однопоточным или даже синхронным. Абстракция Spring скрывает детали реализации между окружениями Java SE и Java EE.
Интерфейс TaskExecutor
в Spring идентичен интерфейсу java.util.concurrent.Executor
. На самом деле, изначально основной причиной его существования было абстрагирование от необходимости использования Java 5 при использовании пулов потоков. Интерфейс имеет единственный метод (execute(Runnable task)
), который принимает задачу к выполнению, основываясь на семантике и конфигурации пула потоков.
TaskExecutor
был первоначально создан для того, чтобы предоставить другим компонентам Spring абстракцию для объединения потоков, когда это необходимо. Такие компоненты, как ApplicationEventMulticaster
, AbstractMessageListenerContainer
из JMS и интеграция Quartz, используют абстракцию TaskExecutor
для объединения потоков. Однако если для бинов требуется применение логики работы пула потоков, можно использовать эту абстракцию для собственных нужд.
Типы TaskExecutor
Spring содержит ряд готовых реализаций TaskExecutor
. Скорее всего, внедрять собственные вам никогда не придется. Варианты, которые предусматривает Spring, следующие:
-
SyncTaskExecutor
: Эта реализация не выполняет вызовы асинхронно. Вместо этого каждый вызов происходит в вызывающем потоке. Эта реализация используется в основном в ситуациях, когда многопоточность не требуется, например, в простых тестовых сценариях. -
SimpleAsyncTaskExecutor
: Эта реализация не использует повторно ни один поток. Скорее, она запускает новый поток для каждого вызова. Тем не менее эта реализация поддерживает механизм ограничения параллелизма, который блокирует все вызовы, превышающие лимит, пока не освободится слот. Если требуется настоящее объединение в пул, см.ThreadPoolTaskExecutor
далее в этом списке. -
ConcurrentTaskExecutor
: Эта реализация является адаптером для экземпляраjava.util.concurrent.Executor
. Существует альтернатива (ThreadPoolTaskExecutor
), которая открывает параметры конфигурацииExecutor
как свойства бина. Нeобходимость использоватьConcurrentTaskExecutor
напрямую возникает крайне редко. Однако, еслиThreadPoolTaskExecutor
недостаточно гибок для ваших нужд, альтернативным вариантом будетConcurrentTaskExecutor
. -
ThreadPoolTaskExecutor
: Эта реализация используется наиболее часто. Она открывает свойства бина для конфигурированияjava.util.concurrent.ThreadPoolExecutor
и оборачивает его вTaskExecutor
. Если необходима адаптация к другому типуjava.util.concurrent.Executor
, мы рекомендуем использовать вместо неё реализациюConcurrentTaskExecutor
. -
WorkManagerTaskExecutor
: Эта реализация используетWorkManager
из CommonJ в качестве базового поставщика служб и является центральным вспомогательным классом для настройки интеграции пула потоков на основе CommonJ в WebLogic или WebSphere в контексте приложения Spring. -
DefaultManagedTaskExecutor
: Эта реализация использует полученный через JNDI экземплярManagedExecutorService
в JSR-236-совместимом окружении выполнения (таком как сервер приложений на Java EE 7+), заменяя для этой цели WorkManager из CommonJ.
Использование TaskExecutor
Реализации TaskExecutor
из Spring используются как простые классы JavaBeans. В следующем примере определяется бин, который использует ThreadPoolTaskExecutor
для асинхронного вывода набора сообщений:
import org.springframework.core.task.TaskExecutor;
public class TaskExecutorExample {
private class MessagePrinterTask implements Runnable {
private String message;
public MessagePrinterTask(String message) {
this.message = message;
}
public void run() {
System.out.println(message);
}
}
private TaskExecutor taskExecutor;
public TaskExecutorExample(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
public void printMessages() {
for(int i = 0; i < 25; i++) {
taskExecutor.execute(new MessagePrinterTask("Message" + i));
}
}
}
Как можно заметить, вместо того, чтобы извлекать поток из пула и выполнять его самостоятельно, вы добавляете свой Runnable
в очередь. Затем TaskExecutor
использует свои внутренние правила, чтобы решить, когда выполнять задачу.
Для конфигурирования правил, которые использует TaskExecutor
, мы открываем простые свойства бина:
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5"/>
<property name="maxPoolSize" value="10"/>
<property name="queueCapacity" value="25"/>
</bean>
<bean id="taskExecutorExample" class="TaskExecutorExample">
<constructor-arg ref="taskExecutor"/>
</bean>
Абстракция TaskScheduler
в Spring
В дополнение к абстракции TaskExecutor
, в Spring 3.0 представлена абстракция TaskScheduler
с различными методами планирования задач для выполнения в определенный момент в будущем. В следующем листинге показано определение интерфейса TaskScheduler
:
public interface TaskScheduler {
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture schedule(Runnable task, Instant startTime);
ScheduledFuture schedule(Runnable task, Date startTime);
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
ScheduledFuture scheduleAtFixedRate(Runnable task, long period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}
Самый простой метод – это метод с именем schedule
, который принимает только Runnable
и Date
. Это приводит к тому, что задача выполняется единожды через заданное время. Все остальные методы могут планировать задачи для многократного выполнения. Методы с фиксированной частотой и фиксированной задержкой предназначены для простого периодического выполнения, но метод, принимающий Trigger
, гораздо более гибкий.
Интерфейс Trigger
Интерфейс Trigger
главным образом вдохновлен JSR-236, которая по состоянию на Spring 3.0 еще не была официально реализована. Основная идея Trigger
заключается в том, что время выполнения может быть определено на основе предыдущих результатов выполнения или даже произвольных условий. Если эти определения учитывают результат предыдущего выполнения, то информация об этом становится доступной в TriggerContext
. Сам интерфейс Trigger
довольно прост и показан в следующем листинге:
public interface Trigger {
Date nextExecutionTime(TriggerContext triggerContext);
}
TriggerContext
является наиболее важным компонентом. Он включает в себя все необходимые данные и открыт для расширения в будущем, если это будет необходимо. TriggerContext
– это интерфейс (по умолчанию используется реализация SimpleTriggerContext
). В следующем листинге показаны доступные методы для реализаций Trigger
.
public interface TriggerContext {
Date lastScheduledExecutionTime();
Date lastActualExecutionTime();
Date lastCompletionTime();
}
Реализации интерфейса Trigger
Spring предусматривает две реализации интерфейса Trigger
. Наиболее интересной из них является CronTrigger
. Она позволяет планировать задания на основе выражений формата cron. Например, следующее задание запланировано на 15 минут после каждого часа, но только в рабочие часы с 9 до 5 в будние дни:
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
Другая реализация – это PeriodicTrigger
, который принимает фиксированный период, необязательное значение начальной задержки и булево значение, указывающее, следует ли интерпретировать период как фиксированную частоту или фиксированную задержку. Поскольку интерфейс TaskScheduler
уже определяет методы для планирования задач с фиксированной частотой или с фиксированной задержкой, то эти методы следует использовать напрямую, когда это возможно. Польза реализации PeriodicTrigger
заключается в том, что вы можете использовать ее внутри компонентов, которые прибегают к абстракции Trigger
. Например, удобнее может быть разрешить использовать периодические триггеры, триггеры на основе выражений в формате cron и даже пользовательские реализации триггеров как взаимозаменяемые. Такой компонент мог бы задействовать преимущества внедрения зависимостей, чтобы можно было настраивать такие Triggers
извне и, следовательно, легко изменять или расширять их.
Реализации TaskScheduler
Как и в случае с абстракцией TaskExecutor
для Spring, основное преимущество структуры TaskScheduler
заключается в том, что потребности приложения в планировании отделены от окружения развертывания. Этот уровень абстракции особенно актуален при развертывании в окружении сервера приложений, где самому приложению не нужно непосредственно создавать потоки. Для таких сценариев Spring предусматривает TimerManagerTaskScheduler
, который делегирует полномочия TimerManager
из CommonJ в WebLogic или WebSphere, а также более свежий DefaultManagedTaskScheduler
, который делегирует полномочия ManagedScheduledExecutorService
из JSR-236 в окружении Java EE 7+. Оба варианта обычно конфигурируются при помощи поиска через JNDI.
Если нет нужды во внешнем управлении потоками, то более простой альтернативой является локальная служба ScheduledExecutorService
, установленная внутри приложения, которую можно адаптировать с помощью ConcurrentTaskScheduler
из Spring. Из соображений удобства Spring также предусматривает ThreadPoolTaskScheduler
, который на внутреннем уровне делегирует полномочия службе ScheduledExecutorService
для обеспечения общей конфигурации в стиле бинов, подобно ThreadPoolTaskExecutor
. Эти варианты прекрасно работают и для локально встроенных пулов потоков в окружениях серверов приложений с опережающими вычислениями – в частности, в Tomcat и Jetty.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ