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>