Sdscompany.ru

Компьютерный журнал
33 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Планировщик задач java

Выполнение задач по расписанию

Этот урок освещает пошаговое создание задач по расписанию с использованием Spring.

Что вы создадите

Вы создадите приложение, которое будет печатать текущее время каждые пять секунд при помощи использования Spring-аннотации @Scheduled .

Что вам потребуется

  • Примерно 15 минут свободного времени
  • Любимый текстовый редактор или IDE
  • JDK 6 и выше
  • Gradle 1.11+ или Maven 3.0+
  • Вы также можете импортировать код этого урока, а также просматривать web-страницы прямо из Spring Tool Suite (STS), собственно как и работать дальше из него.

Как проходить этот урок

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

Чтобы начать с нуля, перейдите в Настройка проекта.

Чтобы пропустить базовые шаги, выполните следующее:

  • Загрузите и распакуйте архив с кодом этого урока, либо кнонируйте из репозитория с помощью Git: git clone https://github.com/spring-guides/gs-scheduling-tasks.git
  • Перейдите в каталог gs-scheduling-tasks/initial
  • Забегая вперед, создайте задачу

Когда вы закончите, можете сравнить получившийся результат с образцом в gs-scheduling-tasks/complete .

Настройка проекта

Для начала вам необходимо настроить базовый скрипт сборки. Вы можете использовать любую систему сборки, которая вам нравится для сборки проетов Spring, но в этом уроке рассмотрим код для работы с Gradle и Maven. Если вы не знакомы ни с одним из них, ознакомьтесь с соответсвующими уроками Сборка Java-проекта с использованием Gradle или Сборка Java-проекта с использованием Maven.

Создание структуры каталогов

В выбранном вами каталоге проекта создайте следующую структуру каталогов; к примеру, командой mkdir -p src/main/java/hello для *nix систем:

Создание файла сборки Gradle

Ниже представлен начальный файл сборки Gradle. Файл pom.xml находится здесь. Если вы используете Spring Tool Suite (STS), то можете импортировать урок прямо из него.

Spring Boot gradle plugin предоставляет множество удобных возможностей:

  • Он собирает все jar’ы в classpath и собирает единое, исполняемое «über-jar», что делает более удобным выполнение и доставку вашего сервиса
  • Он ищет public static void main() метод, как признак исполняемого класса
  • Он предоставляет встроенное разрешение зависимостей, с определенными номерами версий для соответсвующих Spring Boot зависимостей. Вы можете переопределить на любые версии, какие захотите, но он будет по умолчанию для Boot выбранным набором версий

Создание задачи по расписанию

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

Ключевыми компонентами, отвечающими за создание задач по расписанию, являются аннотации @EnableScheduling и @Scheduled .

@EnableScheduling гарантирует, что фоновые задачи создадуться. Без неё ничего не получится.

Используя @Scheduled , вы настраиваете, когда конкретный метод будет запущен. В этом примере используется fixedRate , который определяет интервал между вызовами метода, начиная с момента начала работы каждого из вызовов этого метода. Есть и другие параметры, например, fixedDelay , который определяет интервал между вызовами, начиная с момента окончания работы каждого из вызовов метода. Вы можете также использовать @Scheduled(cron=». . .») выражения для более конкретной установки расписания .

Создание приложения исполняемым

Несмотря на то, что запланированные задачи могут быть в составе web-приложения и WAR файлов, более простой подход, продемонстрированный ниже создает отдельное самостоятельное приложение. Вы упаковываете все в единый, исполняемый JAR-файл, который запускается через хорошо знакомый старый main() Java-метод.

На данном этапе вы создаете новый SpringApplication и запускаете его с ScheduledTasks , которые вы определили ранее. Это действие создает задание на выполнение и позволяет задачам запускаться по расписанию.

Сборка исполняемого JAR

Вы можете собрать единый исполняемый JAR-файл, который содержит все необходимые зависимости, классы и ресурсы. Это делает его легким в загрузке, версионировании и развертывании сервиса как приложения на протяжении всего периода разработки, на различных средах и так далее.

Затем вы можете запустить JAR-файл:

Если вы используете Maven, вы можете запустить приложение, используя mvn spring-boot:run , либо вы можете собрать приложение с mvn clean package и запустить JAR примерно так:

Запуск сервиса

Если вы используете Gradle, вы можете запустить ваш сервис из командной строки:

Как вариант, вы можете запустить ваш сервис напрямую из Gradle примерно так:

Выходные данные отображены. Вы должны увидеть ваши выполненные задачи каждые 5 секунд:

Поздравляем! Вы только что создали приложение с запланированной задачей. Сверьте, насколько рабочий код короче, чем файл сборки! Эта техника работает в любом типе приложений.

Планирование заданий с помощью Quartz

Quartz API использует многосторонний подход к планированию заданий в приложениях Java

Так как число и сложность современных Web-приложений продолжают возрастать, возрастает и сложность всех лежащих в основе приложения компонентов. Планирование заданий является общим требованием для приложений Java в современных системах, и потому оно — предмет постоянной заботы для разработчиков Java. Хотя современные технологии планирования заданий усовершенствовались по сравнению с примитивными триггерами баз данных и отдельными потоками планировщиков, планирование заданий остается нетривиальной проблемой. Одно из наиболее эффективных решений данной проблемы предлагается Quartz API, разработанный OpenSymphony.

Quartz представляет собой среду разработки для планирования заданий с открытым исходным кодом, которая обеспечивает простой, но эффективный механизм планирования заданий в приложениях Java. Quartz представляет разработчикам возможность планировать задания по временному интервалу или по времени суток. В ней реализованы отношения типа «многие-ко-многим» для заданий и триггеров, что позволяет соединять множественные задания с различными триггерами. Приложения, в которые входит Quartz, могут повторно использовать задания из разных событий и группировать множественные задания для одного события. Вы можете настроить Quartz через конфигурационный файл (в котором вы можете задать источник данных для JDBC-транзакций, для глобального подписчика на задания и/или триггеры, плагинов, пулов потоков и т. д.), при этом не происходит интеграции с контекстом или ссылками сервера приложений. Положительным результатом здесь является то, что задания не получают доступа к внутренним функциям Web-сервера; например, в случае с сервером приложений WebSphere задания, запланированные с помощью Quartz, не мешают источникам данных сервера и кэшированию (Dyna-cache).

В статье рассматривается Quartz API с использованием набора примеров кода, которые показывают работу таких механизмов, как задания, триггеры, хранилища заданий и свойства.

Начало работы

Чтобы начать работу с Quartz, вы должны сконфигурировать ваш проект для работы с Quartz API. Данная процедура выглядит следующим образом:

  1. Загрузите Quartz API.
  2. Распакуйте quartz-x.x.x.jar и поместите его в папку вашего проекта или в папку, на которую указывает путь к классам вашего проекта.
  3. Поместите jar-файлы из базовой или дополнительной папки в папку вашего проекта или в папку, на которую указывает путь к классам вашего проекта.
  4. Если вы используете JDBCJobStore , поместите все jar-файлы JDBC в папку вашего проекта или в папку, на которую указывает путь к классам вашего проекта.

Для вашего удобства мы скомпилировали все необходимые файлы, включая JDBC-файлы для DB2, в единый zip-файл. Обратитесь к разделу Загрузка, чтобы загрузить код.

Теперь давайте посмотрим на основные компоненты Quartz API.

Задания и триггеры

Два основные единицы пакета планирования Quartz — это задания и триггеры. Задание представляет собой исполняемую задачу, которая может быть запланирована, а триггер обеспечивает график выполнения задания. Хотя эти две сущности можно было бы объединить, в Quartz они разделены намеренно, что приносит определенную пользу.

Отделив выполнение работы от графика ее выполнения, Quartz дает вам возможность изменить запланированный триггер для отдельного задания без потери самого задания или его контекста. Кроме того, любое отдельное задание может быть связано с множеством триггеров.

Читать еще:  Java строку в массив char

Пример 1: Задания

Вы можете сделать Java-класс исполняемым, реализовав интерфейс org.quartz.job . Пример задания в Quartz приводится в Листинге 1. Этот класс замещает метод execute(JobExecutionContext context) очень простым оператором вывода. Этот метод может содержать любой код, который мы хотели бы выполнить. (Все примеры кода основаны на Quartz 1.5.2, стабильной версии на момент написания этой статьи.)

Листинг 1. SimpleQuartzJob.java

Обратите внимание, что метод execute принимает объект JobExecutionContext в качестве аргумента. Этот объект обеспечивает контекст выполнения экземпляра задания. В частности он представляет доступ к планировщику и триггеру, которые совместно использовались для начала выполнения задания, а также объекта JobDetail задания. Quartz отделяет выполнение задания от состояния окружения задания, помещая состояние в объект JobDetail и используя конструктор JobDetail для запуска экземпляра задания. В объекте JobDetail хранятся подписчики на задания, группа, отображение данных, описание и другие свойства задания.

Пример 2: Простые триггеры

Триггер создает график исполнения задания. Quartz предлагает для триггеров несколько различных опций различной степени сложности. SimpleTrigger в Листинге 2 знакомит с основами триггеров:

Листинг 2. SimpleTriggerRunner.java

Листинг 2 начинается с создания экземпляра SchedulerFactory и получения планировщика. Как уже обсуждалось ранее, объект JobDetail создается приписыванием Job в качестве аргумента к его конструктору. Как видно из названия, экземпляр SimpleTrigger достаточно примитивен. После создания объекта, мы устанавливаем несколько основных свойств, планируя задание на немедленное выполнение, а затем на повторное выполнение каждые 10 секунд до тех пор, пока задание не будет выполнено 100 раз.

Есть еще несколько других способов работы с SimpleTrigger . В дополнение к указанному числу повторов и интервалу между повторами вы можете запланировать выполнение задания в указанное календарное время, задавая максимальное время выполнения или приоритет задания, о чем мы будем говорить ниже. Максимальное время выполнения имеет более высокий приоритет, чем указанное число повторов, гарантируя, таким образом, что выполнение задания не превысит максимальное время.

Пример 3: Триггеры cron

CronTrigger дает возможность более точного планирования, чем SimpleTrigger , но также не является слишком сложным. Основываясь на выражениях cron, CronTrigger дает возможность устанавливать интервалы повторов в календарном виде, а не в стандартном, что является значительным улучшением по сравнению с SimpleTrigger s.

Выражение cron состоит из следующих семи полей:

  • Секунды
  • Минуты
  • Часы
  • День месяца
  • Месяц
  • День недели
  • Год (необязательное поле)

Специальные символы

Триггеры cron используют серию специальных символов, например:

  • Символ косая черта (/) обозначает приращение значения. Например, «5/15» в поле «секунды» означает каждые 15 секунд, начиная с пятой секунды.
  • Знак вопроса (?) и букву L (L) разрешается использовать только в полях «день месяца» и «день недели». Знак вопроса означает, что в поле не должно быть указанной величины. Таким образом, если вы устанавливаете день недели, вы можете вставить «?» в поле «день недели» для обозначения того, что значение «день недели» несущественно. Буква L — это сокращение от last (последний). Если она помещается в поле «день месяца», задание будет запланировано на последний день месяца. В поле «день недели» «L» равнозначно «7», если помещается само по себе, или означает последний экземпляр «дня недели» в этом месяце. Так, «0L» запланирует выполнение задания на последнее воскресенье данного месяца.
  • Буква W (W) в поле «день месяца» планирует выполнение задания на ближайший к заданному значению рабочий день. Введя «1W» в поле «день месяца» вы планируете выполнение задания на рабочий день, ближайший к первому числу месяца.
  • Знак фунта (#) устанавливает конкретный рабочий день данного месяца. Ввод «MON#2» в поле «день недели» планирует задание на второй понедельник месяца.
  • Знак астериска (*) является подстановочным знаком и обозначает, что любое возможное значение может быть принято для данного отдельного поля.

Все эти определения могут привести в уныние, но выражения cron становятся простыми после нескольких минут тренировки.

Листинг 3 показывает пример CronTrigger . Отметьте, что создание экземпляров SchedulerFactory , Scheduler и JobDetail идентичны примеру, SimpleTrigger . В этом случае мы только поменяли триггер. Выражение cron, которое мы задали («0/5 * * * * ?») планирует выполнение задания каждые 5 секунд.

Листинг 3. CronTriggerRunner.java

Продвинутые возможности Quartz

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

Хранилища заданий

Quartz предлагает два различных средства, с помощью которых можно хранить связанные с заданиями и триггерами данные в памяти или в базе данных. Первое средство, экземпляр класса RAMJobStore , является настройкой по умолчанию. Это самое простое в использовании хранилище заданий, дающее к тому же наибольшую производительность, поскольку все данные хранятся в памяти. Главным недостатком этого метода является недостаточная сохраняемость данных. Поскольку все данные сохраняются в RAM, вся информация будет утеряна после «падения» приложения или системы.

Чтобы исправить эти недостатки, Quartz предлагает JDBCJobStore . Как следует из названия, этот способ сохранения заданий помещает данные в базу данных через JDBC. Ценой более надёжного хранения данных является более низкая производительность, а также большая сложность.

Настройка JDBCJobStore

Вы видели то, как действует RAMJobStore в предыдущих примерах. Поскольку это хранилище заданий по умолчанию, ясно, что необходимость в дополнительных настройках отсутствует. Использование JDBCJobStore требует, однако, некоторой инициализации.

Настройка JDBCJobStore для использования в ваших приложениях происходит в два этапа: во-первых, вы должны создать таблицы базы данных для использования при хранилищами заданий. JDBCJobStore совместимо со всеми основными базами данных, а Quartz предлагает для создания таблиц серию SQL-скриптов, облегчающих процесс установки. Вы найдете SQL-скрипты для создания таблиц в папке «docs/dbTables» дистрибутива Quartz. Во вторых, вы должны определить некоторые свойства, показанные в Таблице 1:

Working With the Java Scheduler

Using the Java scheduler has never been easier.

Join the DZone community and get the full member experience.

In this article, we are going to cover the following topics pertaining to the Java Scheduler:

  • Scheduling a task in Java
  • SchedularConfigurer vs. @Scheduled
  • Changing the cron expression dynamically
  • Dependency execution between two tasks

Scheduling a Task in Java

The scheduler is used to schedule a thread or task that executes at a certain period of time or periodically at a fixed interval. There are multiple ways to schedule a task in Java.

  • java.util.TimerTask
  • java.util.concurrent.ScheduledExecutorService
  • Quartz Scheduler
  • org.springframework.scheduling.TaskScheduler

TimerTask is executed by a demon thread. Any delay in a task can delay the other task in a schedule. Hence, it is not a viable option when multiple tasks need to be executed asynchronously at a certain time.

Let’s look at an example:

In the above execution, it is clear that task 2 gets stuck because the thread that is handling task1 is going to sleep for 10 secs. Hence, there is only one demon thread that is working on both task 1 and task 2, and if one gets hit, all the tasks will be pushed back.

ScheduledExecutorService and TaskScheduler works in the same manner. The only difference from the former is the Java library and the latter is the Spring framework. So if the application is in Spring, the TaskScheduler can be a better option to schedule jobs.

Читать еще:  Java построение графиков по точкам

Now, let’s see the usage of the TaskScheduler interface and we can use it in Spring.

SchedularConfigurer Vs. @Scheduled

Spring provides an annotation-based scheduling with the help of @Scheduled .

The threads are handled by the Spring framework, and we will not have any control over the threads that will work on the tasks. Let’s take a look at the example below:

There is one thread scheduling-1, which is handling both task1 and task2. The moment task1 goes to sleep for 10 seconds, task 2 also waits for it. Hence, if there are two jobs running at the same time, one will wait for another to complete.

Now, we will try writing a scheduler task where we want to execute task1 and task2 asynchronously. There will be a pool of threads and we will schedule each task in the ThreadPoolTaskScheduler . The class needs to implement the SchedulingConfigurer interface. It gives more control to the scheduler threads as compared to @Scheduled .

I am creating two jobs: job1 and job2. Then, I will be scheduling it using TaskScheduler . This time, I am using a Cron expression to schedule the job1 at every five-second interval and job2 every second. Job1 gets stuck for 10 seconds and we will see job2 still running smoothly without interruption . We see that both task1 and task 2 are being handled by a pool of threads which is created using ThreadPoolTaskScheduler

Changing a Cron Expression Dynamically

We can always keep the cron expression in a property file using the Spring config. If the Spring Config server is not available, we can also fetch it from theDB. Any update of the cron expression will update the Scheduler. But in order to cancel the current schedule and execute the new schedule, we can expose an API to refresh the cron job:

Additionally, you can invoke the method from any controller to refresh the cron schedule.

Dependency Execution Between Two Tasks

So far, we know that we can execute the jobs asynchronously using the TaskScheduler and Schedulingconfigurer interface. Now, let’s say we have job1 that runs for an hour at 1 am and job2 that runs at 2 am. But, job2 should not start unless job1 is complete. We also have another list of jobs that can run between 1 and 2 am and are independent of other jobs.

Let’s see how we can create a dependency between job1 and job2, yet run all jobs asynchronously at the scheduled time.

First, let’s declare a volatile variable:

We chose to use a volatile boolean flag so that it is not cached in the thread-local but is saved in the main memory and can be used by all threads in the pool. Based on the flag, job2 waits indefinitely until job1 is complete. Now, if job1 hangs, there is a chance that job2 will wait indefinitely.

Conclusion

There are various ways to schedule in Java, and now, we know how to use the scheduler API and the Spring scheduler API to control threads in the pool.

Многопоточное программирование в Java 8. Часть первая. Параллельное выполнение кода с помощью потоков

    Переводы, 8 июля 2015 в 16:58

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

Впервые Concurrency API был представлен вместе с выходом Java 5 и с тех пор постоянно развивался с каждой новой версией Java. Большую часть примеров можно реализовать на более старых версиях, однако в этой статье я собираюсь использовать лямбда-выражения. Если вы все еще не знакомы с нововведениями Java 8, рекомендую посмотреть мое руководство.

Потоки и задачи

Все современные операционные системы поддерживают параллельное выполнение кода с помощью процессов и потоков. Процесс — это экземпляр программы, который запускается независимо от остальных. Например, когда вы запускаете программу на Java, ОС создает новый процесс, который работает параллельно другим. Внутри процессов мы можем использовать потоки, тем самым выжав из процессора максимум возможностей.

Потоки (threads) в Java поддерживаются начиная с JDK 1.0. Прежде чем запустить поток, ему надо предоставить участок кода, который обычно называется «задачей» (task). Это делается через реализацию интерфейса Runnable , у которого есть только один метод без аргументов, возвращающий void — run() . Вот пример того, как это работает:

Поскольку интерфейс Runnable функциональный, мы можем использовать лямбда-выражения, которые появились в Java 8. В примере мы создаем задачу, которая выводит имя текущего потока на консоль, и запускаем ее сначала в главном потоке, а затем — в отдельном.

Результат выполнения этого кода может выглядеть так:

Из-за параллельного выполнения мы не можем сказать, будет наш поток запущен до или после вывода «Done!» на экран. Эта особенность делает параллельное программирование сложной задачей в больших приложениях.

Sportmaster Lab, Москва

Потоки могут быть приостановлены на некоторое время. Это весьма полезно, если мы хотим сэмулировать долго выполняющуюся задачу. Например, так:

Когда вы запустите этот код, вы увидите секундную задержку между выводом первой и второй строки на экран. TimeUnit — полезный класс для работы с единицами времени, но то же самое можно сделать с помощью Thread.sleep(1000) .

Работать с потоками напрямую неудобно и чревато ошибками. Поэтому в 2004 году в Java 5 добавили Concurrency API. Он находится в пакете java.util.concurrent и содержит большое количество полезных классов и методов для многопоточного программирования. С тех пор Concurrency API непрерывно развивался и развивается.

Давайте теперь подробнее рассмотрим одну из самых важных частей Concurrency API — сервис исполнителей (executor services).

Исполнители

Concurrency API вводит понятие сервиса-исполнителя (ExecutorService) — высокоуровневую замену работе с потоками напрямую. Исполнители выполняют задачи асинхронно и обычно используют пул потоков, так что нам не надо создавать их вручную. Все потоки из пула будут использованы повторно после выполнения задачи, а значит, мы можем создать в приложении столько задач, сколько хотим, используя один исполнитель.

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

Класс Executors предоставляет удобные методы-фабрики для создания различных сервисов исполнителей. В данном случае мы использовали исполнитель с одним потоком.

Результат выглядит так же, как в прошлый раз. Но у этого кода есть важное отличие — он никогда не остановится. Работу исполнителей надо завершать явно. Для этого в интерфейсе ExecutorService есть два метода: shutdown() , который ждет завершения запущенных задач, и shutdownNow() , который останавливает исполнитель немедленно.

Вот как я предпочитаю останавливать исполнителей:

Исполнитель пытается завершить работу, ожидая завершения запущенных задач в течение определенного времени (5 секунд). По истечении этого времени он останавливается, прерывая все незавершенные задачи.

Callable и Future

Кроме Runnable , исполнители могут принимать другой вид задач, который называется Callable . Callable — это также функциональный интерфейс, но, в отличие от Runnable , он может возвращать значение.

Давайте напишем задачу, которая возвращает целое число после секундной паузы:

Читать еще:  Интегрирование метод трапеций javascript

Callable-задачи также могут быть переданы исполнителям. Но как тогда получить результат, который они возвращают? Поскольку метод submit() не ждет завершения задачи, исполнитель не может вернуть результат задачи напрямую. Вместо этого исполнитель возвращает специальный объект Future, у которого мы сможем запросить результат задачи.

После отправки задачи исполнителю мы сначала проверяем, завершено ли ее выполнение, с помощью метода isDone() . Поскольку задача имеет задержку в одну секунду, прежде чем вернуть число, я более чем уверен, что она еще не завершена.

Вызов метода get() блокирует поток и ждет завершения задачи, а затем возвращает результат ее выполнения. Теперь future.isDone() вернет true , и мы увидим на консоли следующее:

Задачи жестко связаны с сервисом исполнителей, и, если вы его остановите, попытка получить результат задачи выбросит исключение:

Вы, возможно, заметили, что на этот раз мы создаем сервис немного по-другому: с помощью метода newFixedThreadPool(1) , который вернет исполнителя с пулом в один поток. Это эквивалентно вызову метода newSingleThreadExecutor() , однако мы можем изменить количество потоков в пуле.

Таймауты

Любой вызов метода future.get() блокирует поток до тех пор, пока задача не будет завершена. В наихудшем случае выполнение задачи не завершится никогда, блокируя ваше приложение. Избежать этого можно, передав таймаут:

Выполнение этого кода вызовет TimeoutException :

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

InvokeAll

Исполнители могут принимать список задач на выполнение с помощью метода invokeAll() , который принимает коллекцию callable-задач и возвращает список из Future .

В этом примере мы использовали функциональные потоки Java 8 для обработки задач, возвращенных методом invokeAll . Мы прошлись по всем задачам и вывели их результат на консоль. Если вы не знакомы с потоками (streams) Java 8, смотрите мое руководство.

InvokeAny

Другой способ отдать на выполнение несколько задач — метод invokeAny() . Он работает немного по-другому: вместо возврата Future он блокирует поток до того, как завершится хоть одна задача, и возвращает ее результат.

Чтобы показать, как работает этот метод, создадим метод, эмулирующий поведение различных задач. Он будет возвращать Callable , который вернет указанную строку после необходимой задержки:

Используем этот метод, чтобы создать несколько задач с разными строками и задержками от одной до трех секунд. Отправка этих задач исполнителю через метод invokeAny() вернет результат задачи с наименьшей задержкой. В данном случае это «task2»:

В примере выше использован еще один вид исполнителей, который создается с помощью метода newWorkStealingPool() . Этот метод появился в Java 8 и ведет себя не так, как другие: вместо использования фиксированного количества потоков он создает ForkJoinPool с определенным параллелизмом (parallelism size), по умолчанию равным количеству ядер машины.

ForkJoinPool впервые появился в Java 7, и мы рассмотрим его подробнее в следующих частях нашего руководства. А теперь давайте посмотрим на исполнители с планировщиком (scheduled executors).

Исполнители с планировщиком

Мы уже знаем, как отдать задачу исполнителю и получить ее результат. Для того, чтобы периодически запускать задачу, мы можем использовать пул потоков с планировщиком.

ScheduledExecutorService способен запускать задачи один или несколько раз с заданным интервалом.

Этот пример показывает, как заставить исполнитель выполнить задачу через три секунды:

Когда мы передаем задачу планировщику, он возвращает особый тип Future — ScheduledFuture , который предоставляет метод getDelay() для получения оставшегося до запуска времени.

У исполнителя с планировщиком есть два метода для установки задач: scheduleAtFixedRate() и scheduleWithFixedDelay() . Первый устанавливает задачи с определенным интервалом, например, в одну секунду:

Кроме того, он принимает начальную задержку, которая определяет время до первого запуска.

Обратите внимание, что метод scheduleAtFixedRate() не берет в расчет время выполнения задачи. Так, если вы поставите задачу, которая выполняется две секунды, с интервалом в одну, пул потоков рано или поздно переполнится.

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

В этом примере мы ставим задачу с задержкой в одну секунду между окончанием выполнения задачи и началом следующей. Начальной задержки нет, и каждая задача выполняется две секунды. Так, задачи будут запускаться на 0, 3, 6, 9 и т. д. секунде. Как видите, метод scheduleWithFixedDelay() весьма полезен, если мы не можем заранее сказать, сколько будет выполняться задача.

Это была первая часть серии статей про многопоточное программирование. Настоятельно рекомендую разобрать вышеприведенные примеры самостоятельно. Все они доступны на GitHub. Можете смело форкать репозиторий и добавлять его в избранное.

Надеюсь, вам понравилась статья. Если у вас возникли какие-либо вопросы, вы можете задать их в твиттере.

Планировщик заданий в Spring

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

Итак, для настройки планировщика нужно выполнить весь этот простой рецепт по шагам.

Шаг 1. Get ready for beans

Добавляем в application config следующее:

Обратите внимание на:

Я использую один класс com.service.Scheduler для хранения всех методов-шедулеров приложения. На мой вкус, это гораздо приятнее и удобнее, когда все методы, требующие планирования не разбросаны по разным классам, а сконцентрированы в одном.

Шаг 2. Добавить класс-шедулер

Создаем в проекте класс com.service.Scheduler для хранения шедулеров-методов. Наполним его парой-тройкой методов, требующих выполнения по расписанию.

А теперь немного об аннотации @Scheduled.

В качестве параметров ей нужно указать одно из трех значений, указывающее на то, когда нужно выполнить метод:

fixedRate — метод выполняется с фиксированной частотой (в милисекундах). Важно понимать, что метод выполнится даже тогда, когда предыдущий вызов метода еще не завершился;

fixedDelay — метод выполняется с фиксированной задержкой (в милисекундах). В отличие от fixedRate, указывает на то, что метод должен выполнится через указанное количество времени после того, как его предыдущий вызов был завершен;

cron — unix-фича, подробнее о ней ниже.

Вот и весь рецепт. Приправьте класс-шедулер нужными вам методами, доведите до готовности проект, скомпилируйте и наслаждайтесь результатом. Теперь напоследок остановимся немного подробнее на параметре cron.

Cron и с чем его едят

Дословно из вики:

cron — демон-планировщик заданий в Unix-подобных операционных системах, использующийся для периодического выполнения заданий в определённое время.

В Spring этот параметр устроен похожим образом. Однако, чтобы википедия вас не запутала, рассмотрим некоторые тонкости.

Вики приводит следующую картинку формата cron в unix:

Доверившись такому формату, я написал следующий триггер крон для метода-планировщика:

Но в 11:00 я так и не увидел, как мой метод вызывается. Посетив сайт: http://quartz-scheduler.org/documentation/quartz-1.x/tutorials/crontrigger я попробовал следующий формат:

То есть для моего примера получилось следующее:

It works! Метод вызвался ровно в 11:00:00. С википедией получилось как всегда: “Доверяй, но проверяй”.

На сайте quartz`а вы можете подчерпнуть всю исчерпывающую информацию о формате cron. Остановлюсь вкратце лишь на самом важном:

* — выбирает все величины. То есть на месте позиции часа символ * означает, что задание будет выполняться каждый час;

, — отделяет дополнительные величины. Например, триггер “0 0 11,12 * * ?” будет срабатывать в 11 и 12 часов;

/ — определяет инкремент величины. Например, “0 0 0/2 * * ?” означает, что триггер будет срабатывать каждые 2 часа.

Ссылка на основную публикацию
Adblock
detector