Энциклопедия схем запланированных задач

Java

Оригинальный адрес:crossoverjie.top

предисловие

Перед фестивалем обновление соответствующей статьи по запланированному заданию«Колесо времени задержанных сообщений», друг предположил, что надеется полностью внедрить общую схему задач на время, поэтому у него появилась эта статья.

Timer

На этой встрече в основном будут обсуждаться решения, которыми вы чаще всего пользуетесь, первое —TimerТаймер, который может запускать задачу через заданное время или периодически, способ использования тоже очень прост:

Таким образом, можно создать две простые временные задачи соответственно в3s/5sбеги после этого.

Он действительно прост в использовании, но есть и много проблем.Если вы хотите понять его проблемы, вы должны сначала понять принцип его реализации.

Принцип реализации

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

существуетTimerимеет встроенныйTaskQueueОчередь, используемая для хранения всех запланированных задач.

По сути, это массив最小堆, что позволяет каждый раз записывать запланированные задачи и сортировать их по времени выполнения, гарантируя, что время выполнения задач в верхней части кучи будет наименьшим.

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

Объединение кода будет проще для понимания:

При написании задачи будут сохранены некоторые основные атрибуты (время планирования задачи, период, статус задачи инициализации и т. д.), и, наконец, задача будет записана в эту встроенную очередь.

Основной метод в процессе написания задачи заключается в следующем.fixUp(), он будет сравнивать записанную задачу из середины очереди с предыдущей задачей по времени выполнения и постоянно сравнивать ее вперед.

Если это время является самым ранним выполнением, оно будет перемещено на вершину кучи последним.

По этому процессу видно, чтоTimerВременная сложность добавления новой задачи составляет.

Давайте посмотрим на процесс выполнения задачи, по сути это инициализацияTimerкогда он запускает поток в фоновом режиме дляTaskQueueПолучить задачи из очереди для планирования.

Так что нам просто нужно увидеть егоrun()Вот и все.

Из этого кода видно, что этот поток постоянно вызывает

task = queue.getMin();

чтобы получить задание и, наконец, использоватьtask.run()выполнить задание.

отgetMin()В методе видно, что он согласуется с тем, что мы говорили ранее, каждый раз, когда выполняется задача извлечения вершины кучи.

Как только время выполнения извлеченной задачи соответствует требованиям, ее можно запускать, и ее нужно удалить из очереди, реализуемой этой минимальной кучей, т.queue.removeMin()метод.

На самом деле его основной принцип аналогичен написанию задач, за исключением того, что задачи в конце кучи поднимаются на вершину кучи, а затем задачи перемещаются назад по сравнению, пока не достигнут соответствующей позиции.

Из процесса написания и удаления задач видно, что эта минимальная куча упорядочена лишь относительно, а не абсолютно.

Прочитав исходный код, вы, естественно, сможете найти его проблемы.

  • Существует только один поток для планирования задач в фоновом режиме, поэтому задача заблокирована и выполняется.Если цикл выполнения одной из задач слишком длинный, это повлияет на другие задачи.
  • Timerне перехватывает другие исключения сам по себе (перехватывает толькоInterruptedException), если в задаче возникает исключение (например, нулевой указатель), последующие задачи выполняться не будут.

ScheduledExecutor

теперь, когдаTimerЕсть некоторые проблемы, поэтому вJDK1.5Представлен в пакете concurrent вScheduledThreadPoolExecutorзаменитьTimer, из пути к пакету, где он находится, видно, что он поддерживает параллельное выполнение задач.

Давайте сначала посмотрим на его диаграмму наследования классов:

Вы можете видеть, что это также сам пул потоков, наследующийThreadPoolExecutor.

Из его конструктора также видно, что по сути создается пул потоков, но очередь блокировки в этом пуле потоков является настраиваемой очередью задержкиDelayedWorkQueueTimerсерединаTaskQueueтот же эффект)

Новое задание

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

При создании новой задачи она в итоге будет вызванаoffer()метод, также используемый здесьsiftUp()Переместите задачу записи на вершину кучи.

Принцип тот же, что и раньшеTimerАналогично, но здесь сортируется кастомным компаратором, явно сравнивается по времени выполнения задачи.

запустить задачу

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

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

В пуле потоков фоновый поток при инициализации будет использоваться для получения задач из очереди блокировки, но здесь очередь блокировки становитсяDelayedWorkQueue, поэтому задачи, которые выносятся каждый раз, должны быть отсортированы по времени выполнения.

иTimerАналогично вызываться после снятия задачиfinishPoll()Чтобы удалить, нужно упомянуть последнюю задачу в верхней части стека, а затем сравнить и переместить в соответствующую позицию по одному.

И вызвать потребление этогоDelayedWorkQueueМесто в очереди — при написании задач.

По сути, вызовThreadPoolExecutorизaddWorker()Писать задачи, так потреблятьDelayedWorkQueueтакже запускается в нем.

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

Прочитав принцип, вы должны знать иTimerГде преимущество.

Timer ScheduledThreadPoolExecutor
блокировка одного потока Многопоточные задачи не влияют друг на друга
Задача останавливается при исключении В зависимости от пула потоков исключение в одной задаче не влияет на другие задачи.

Поэтому, когда возникает потребность в задачах на время, очевидно, что ее следует устранить.Timer.

колесо времени

Последнее — это задание на определение времени на основе колеса времени, о котором я писал в предыдущей статье.«Колесо времени задержанных сообщений»был подробно представлен.

С помощью анализа исходного кода мы также можем провести сравнение:

ScheduledThreadPoolExecutor на основе колеса времени
Напишите эффективность Исходя из минимальной кучи, чем больше задач, тем ниже эффективность иHashMapНаписание аналогично и эффективность очень высока.
эффективность Первый вынимается каждый раз, что очень эффективно Переключайте указатель каждую секунду, чтобы снять задачу

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

Но когда задач малоScheduledThreadPoolExecutorНеплохо, в конце концов, он не потребляет указатель каждую секундуCPU, но как только задач не будет, поток заблокируется до тех пор, пока не будет записана новая задача.

Обновление RingBufferWheel

в предыдущем«Колесо времени задержанных сообщений»Инструмент задания времени на основе колеса времени настраивается вRingBufferWheel, по подсказке пользователей сети в этот раз я тоже внес некоторые коррективы, оптимизировал API и добавил API для отмены задач.

В предыдущем API каждый раз, когда добавлялась новая задача, она вызываласьstart(), что кажется странным; на этот раз объедините функцию запуска непосредственно вaddTask, более разумно использовать.

В то же время написание задач также поддерживает параллелизм.

Однако здесь следует отметить, чтоstart()Он может быть выполнен только один раз, когда он выполняется одновременно, поэтому он используетсяCASЧтобы гарантировать, что только один поток может успешно выполняться одновременно.

В то же время при добавлении задачи он вернетtaskId, вы можете использовать этот идентификатор для достижения необходимости отмены задачи (хотя это относительно редко), метод использования следующий:

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

Распределенные задачи синхронизации

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

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

На рынке также есть много популярных решений с открытым исходным кодом:

Лично я использовал в своей работе только первые два, и они вполне могут удовлетворить потребности распределенного планирования, такие как высокая доступность, унифицированное управление, лог-алармы и т. д.

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

Заинтересованные друзья могут самостоятельно проверить исходный код или официальные документы.

Суммировать

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

Весь исходный код, задействованный в этой статье:

GitHub.com/crossover J я…

Ваши лайки и репост - лучшая поддержка для меня