6 реализаций задач на время в Java, сколько вы знаете?

Java задняя часть

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

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

Поток ожидает реализации

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

public class Task {

    public static void main(String[] args) {
        // run in a second
        final long timeInterval = 1000;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("Hello !!");
                    try {
                        Thread.sleep(timeInterval);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

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

JDK поставляется с реализацией таймера

В настоящее время Timer API, поставляемый с JDK, является старейшей реализацией задач на время. Таймер — это инструмент таймера, используемый для планирования выполнения определенных задач в фоновом потоке. Он может планировать задачи для периодического «выполнения один раз» или «выполнения несколько раз».

В реальной разработке часто требуются некоторые периодические операции, например выполнение операции каждые 5 минут. Наиболее удобный и эффективный способ реализовать такую ​​операцию — использовать инструментальный класс java.util.Timer.

основной метод

Основные методы класса Timer следующие:

// 在指定延迟时间后执行指定的任务
schedule(TimerTask task,long delay);

// 在指定时间执行指定的任务。(只执行一次)
schedule(TimerTask task, Date time);

// 延迟指定时间(delay)之后,开始以指定的间隔(period)重复执行指定的任务
schedule(TimerTask task,long delay,long period);

// 在指定的时间开始按照指定的间隔(period)重复执行指定的任务
schedule(TimerTask task, Date firstTime , long period);

// 在指定的时间开始进行重复的固定速率执行任务
scheduleAtFixedRate(TimerTask task,Date firstTime,long period);

// 在指定的延迟后开始进行重复的固定速率执行任务
scheduleAtFixedRate(TimerTask task,long delay,long period);

// 终止此计时器,丢弃所有当前已安排的任务。
cancal();

// 从此计时器的任务队列中移除所有已取消的任务。
purge();

Пример использования

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

public class DoSomethingTimerTask extends TimerTask {

    private String taskName;

    public DoSomethingTimerTask(String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        System.out.println(new Date() + " : 任务「" + taskName + "」被执行。");
    }
}

Укажите задержку для выполнения один раз

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

public class DelayOneDemo {

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new DoSomethingTimerTask("DelayOneDemo"),1000L);
    }
}

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

Выполнять через равные промежутки времени

Запланированная задача начинает выполняться в указанное время задержки, а запланированная задача выполняется через равные промежутки времени. Например: задержка выполнения на 2 секунды, а фиксированный интервал выполнения 1 секунда.

public class PeriodDemo {

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new DoSomethingTimerTask("PeriodDemo"),2000L,1000L);
    }
}

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

Исполнение с фиксированной ставкой

Запланированная задача начинает выполняться в указанное время задержки, а запланированная задача выполняется с фиксированной скоростью. Например: задержка выполнения на 2 секунды, а фиксированная скорость 1 секунда.

public class FixedRateDemo {

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new DoSomethingTimerTask("FixedRateDemo"),2000L,1000L);
    }
}

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

На данный момент вам интересно, имеет ли schedule тот же эффект, что и scheduleAtFixedRate, зачем предоставлять два метода и в чем разница между ними?

Разница между расписанием и scheduleAtFixedRate

Прежде чем понять разницу между методами schedule и scheduleAtFixedRate, давайте взглянем на их сходство:

  • Время выполнения задачи не истекло, время следующего выполнения = время начала последнего выполнения + период;
  • Время выполнения задачи истекло, время следующего выполнения = время окончания последнего выполнения;

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

Разница между ними в том, что фокус разный: метод schedule фокусируется на поддержании стабильности времени интервала, а метод scheduleAtFixedRate больше фокусируется на поддержании стабильности частоты выполнения.

Расписание фокусируется на поддержании стабильного времени интервала

Метод расписания вызовет задержку следующих запланированных задач из-за задержки предыдущей задачи. Формула расчета: ScheduleExecutionTime(n+1th) = realExecutionTime(nth) + periodTime.

То есть, если время выполнения по какой-то причине слишком велико, когда задача выполняется в n-й раз, а systemCurrentTime>= ScheduleExecutionTime (n+1-й раз) после завершения выполнения, то временной интервал не будет ожидать в это время, и время выполнения будет выполнено немедленно n+1 задач.

А запланированное время выполнения (n+2th) следующей n+2-й задачи становится realExecutionTime (n+1th)+periodTime. Этот метод больше фокусируется на поддержании стабильного времени интервала.

scheduleAtFixedRate поддерживает стабильную частоту выполнения

Когда scheduleAtFixedRate многократно выполняет план задачи, изначально определяется запланированное время выполнения каждого выполнения этой задачи, т. е. ScheduleExecutionTime (n-й раз) = firstExecuteTime + n*periodTime.

Если задача выполняется в n-й раз, время выполнения по какой-то причине слишком велико, а systemCurrentTime >= ScheduleExecutionTime (n+1-й раз) после завершения выполнения, то период ожидания интервала в это время не выполняется , и n+1-й раз выполняется немедленно.

Запланированное время выполнения (n+2th) следующей задачи n+2 равно firstExecuteTime+(n+2)*periodTime, которое определяется при первом выполнении задачи. Проще говоря, этот метод больше ориентирован на поддержание стабильной частоты выполнения.

Если вы используете одно предложение для описания разницы между schedule и scheduleAtFixedRate после тайм-аута выполнения задачи: стратегия расписания заключается в том, что если вы пропустите его, вы пропустите его и будете следовать новому ритму; стратегия scheduleAtFixedRate заключается в том, что если вы пропустите его, постарайтесь догнать первоначальный ритм (установившийся ритм).

Дефекты таймера

Таймер таймера может быть запланированным (выполнение задачи в указанное время), задержкой (задержка 5 секунд для выполнения задачи) и периодическим выполнением задачи (выполнение задачи каждую 1 секунду). Однако у Timer есть некоторые недостатки. Во-первых, поддержка расписания Timer основана на абсолютном, а не относительном времени, поэтому он очень чувствителен к изменениям системного времени.

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

JDK поставляется с ScheduledExecutorService

ScheduledExecutorService – это новый интерфейс для выполнения задач по времени после версии JAVA 1.5. Это класс задач по времени, разработанный на основе пулов потоков. Каждое запланированное задание будет назначено потоку в пуле потоков для выполнения. То есть задачи выполняются одновременно, не влияя друг на друга.

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

ПланированныйЭксексуал, в основном имеет следующие 4 способа:

ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);
<V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit);
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);

Среди них scheduleAtFixedRate и scheduleWithFixedDelay более удобны при реализации временных программ и используются чаще.

Четыре метода интерфейса, определенные в ScheduledExecutorService, почти такие же, как соответствующие методы в Timer, за исключением того, что запланированный метод Timer должен передаваться в абстрактную задачу TimerTask извне. Инкапсуляция ScheduledExecutorService является более подробной, и уровень инкапсуляции будет выполняться внутри Runnable или Callable, инкапсулируя абстрактный класс задачи (ScheduledFutureTask), аналогичный TimerTask. Затем перейдите в пул потоков и запустите поток для выполнения задачи.

метод scheduleAtFixedRate

Метод scheduleAtFixedRate периодически выполняет задачу с указанной периодичностью. Определение и описание параметра:

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
				long initialDelay,
				long period,
				TimeUnit unit);

Соответствующее значение параметров: command — исполняемый поток; initialDelay — время отложенного выполнения после инициализации; period — минимальный интервал между двумя выполнениями; unit — единица измерения времени.

Пример использования:

public class ScheduleAtFixedRateDemo implements Runnable{

    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(
                new ScheduleAtFixedRateDemo(),
                0,
                1000,
                TimeUnit.MILLISECONDS);
    }

    @Override
    public void run() {
        System.out.println(new Date() + " : 任务「ScheduleAtFixedRateDemo」被执行。");
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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

Это связано с тем, что scheduleAtFixedRate выполняет задачи с интервалами периода.Если время выполнения задачи меньше периода, следующая задача будет выполнена через интервал периода после выполнения последней задачи; но если время выполнения задачи больше периода, предыдущее задание будет выполняться с интервалом в период, после выполнения второго задания сразу же без интервала запустится следующее задание.

метод scheduleWithFixedDelay

Метод scheduleWithFixedDelay выполняет задачу с заданными интервалами частоты. Определение и описание параметра:

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
				long initialDelay,
				long delay,
				TimeUnit unit);

Соответствующее значение параметров: команда — исполняемый поток, initialDelay — время отложенного выполнения после инициализации, период — интервал времени от конца предыдущего выполнения до начала следующего выполнения (время задержки выполнения интервала); единица измерения времени.

Пример использования:

public class ScheduleAtFixedRateDemo implements Runnable{

    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleWithFixedDelay(
                new ScheduleAtFixedRateDemo(),
                0,
                1000,
                TimeUnit.MILLISECONDS);
    }

    @Override
    public void run() {
        System.out.println(new Date() + " : 任务「ScheduleAtFixedRateDemo」被执行。");
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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

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

Реализация кварцевого фреймворка

В дополнение к API, который поставляется с JDK, мы также можем использовать фреймворки с открытым исходным кодом, такие как Quartz.

Quartz — это проект с открытым исходным кодом в области планирования заданий. Quartz можно использовать отдельно или интегрировать с фреймворком Spring, последний обычно используется в реальной разработке. Используя Quartz, вы можете разработать одну или несколько задач на время.Каждой задаче на время можно индивидуально указать время выполнения, например, один раз в 1 час, один раз в 10:00 в первый день каждого месяца и один раз в 5:00. pm в последний день каждого месяца Выполнить один раз и так далее.

Quartz обычно состоит из трех частей: планировщик (Scheduler), задача (JobDetail), триггер (Trigger, включая SimpleTrigger и CronTrigger). Конкретные примеры приведены ниже.

Кварцевая интеграция

Чтобы использовать Quartz, вам сначала нужно ввести соответствующие зависимости в pom-файл проекта:

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
</dependency>
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz-jobs</artifactId>
    <version>2.3.2</version>
</dependency>

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

public class PrintJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println(new Date() + " : 任务「PrintJob」被执行。");
    }
}

Создайте планировщик и триггер и выполните запланированные задачи:

public class MyScheduler {

    public static void main(String[] args) throws SchedulerException {
        // 1、创建调度器Scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        // 2、创建JobDetail实例,并与PrintJob类绑定(Job执行内容)
        JobDetail jobDetail = JobBuilder.newJob(PrintJob.class)
                .withIdentity("job", "group").build();
        // 3、构建Trigger实例,每隔1s执行一次
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger", "triggerGroup")
                .startNow()//立即生效
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(1)//每隔1s执行一次
                        .repeatForever()).build();//一直执行

        //4、Scheduler绑定Job和Trigger,并执行
        scheduler.scheduleJob(jobDetail, trigger);
        System.out.println("--------scheduler start ! ------------");
        scheduler.start();
    }
}

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

В приведенном выше коде Job — это интерфейс Quartz, и реализация бизнес-логики реализуется путем реализации этого интерфейса.

JobDetail привязан к указанному заданию.Каждый раз, когда планировщик планирует и выполняет задание, он сначала получает соответствующее задание, затем создает экземпляр задания, а затем выполняет содержимое execute() в задании. экземпляр объекта будет освобожден и очищен сборщиком мусора JVM.

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

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

Примеры общих выражений Cron:

cron

Видно, что CronTrigger на основе Quartz может реализовать очень богатые сценарии задач синхронизации.

Spring Task

Начиная с Spring 3, Spring поставляется с набором инструментов для задач синхронизации, Spring-Task, который можно рассматривать как облегченный Quartz. Он очень прост в использовании. Никаких дополнительных пакетов не требуется, кроме пакетов, связанных со Spring, и аннотации поддерживаемые и конфигурационные файлы. Обычно в системе Spring для простых задач синхронизации можно напрямую использовать функции, предоставляемые Spring.

Форма, основанная на конфигурационном файле XML, больше не будет представлена, и мы рассмотрим непосредственно реализацию, основанную на форме аннотации. Это очень просто использовать, перейдите непосредственно к коду:

@Component("taskJob")
public class TaskJob {

    @Scheduled(cron = "0 0 3 * * ?")
    public void job1() {
        System.out.println("通过cron定义的定时任务");
    }

    @Scheduled(fixedDelay = 1000L)
    public void job2() {
        System.out.println("通过fixedDelay定义的定时任务");
    }

    @Scheduled(fixedRate = 1000L)
    public void job3() {
        System.out.println("通过fixedRate定义的定时任务");
    }
}

Если это проект Spring Boot, вам нужно добавить @EnableScheduling в класс запуска, чтобы включить запланированные задачи.

В приведенном выше коде @Component используется для создания экземпляра класса, который не имеет ничего общего с временными задачами. @Scheduled указывает, что метод выполняется на основе задач, рассчитанных по времени, а конкретная частота выполнения определяется выражением, указанным cron. Что касается выражения cron, выражение, используемое CronTrigger выше, является согласованным. В отличие от cron, Spring также предоставляет две формы выполнения запланированных задач с фиксированной задержкой и фиксированной скоростью.

Разница между фиксированной задержкой и фиксированной скоростью

Разница между fixedDelay и fixedRate очень похожа на разницу в Timer.

В fixedRate есть концепция расписания. Когда задача запускается, T1, T2 и T3 уже запланированы для выполнения, например, 1 минута, 2 минуты и 3 минуты. Когда время выполнения T1 превышает 1 минуту, она приведет к задержке T2, и T2 будет выполнен сразу после выполнения T1.

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

Недостатки Spring Task

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

Распределенное планирование задач

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

Кварц Распределенный

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

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

Легкий Артефакт XXL-Работа

XXL-JOB — это легкая распределенная платформа планирования задач. Он характеризуется платформизацией, простым развертыванием, быстрой разработкой, простым обучением, легким весом и простым расширением. Центр планирования и функции исполнителя завершают выполнение запланированных задач. Центр планирования отвечает за единое планирование, а исполнитель отвечает за получение и выполнение расписания.

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

другие рамки

Кроме того, есть Elastic-Job, Saturn, SIA-TASK и так далее.

Elastic-Job обладает характеристиками высокой доступности и представляет собой решение для распределенного планирования.

Saturn — это платформа распределенного планирования задач с открытым исходным кодом от Vipshop, которая была преобразована на основе Elastic Job.

SIA-TASK — это распределенная платформа планирования задач CreditEase с открытым исходным кодом.

резюме

В этой статье была разобрана реализация 6 видов временных задач.Что касается применения практических сценариев, то большинство систем были отделены от автономного режима. Для систем, где параллелизм не слишком высок, xxl-job может быть хорошим выбором.

Адрес источника:GitHub.com/colorless/java-…

Профиль блоггера: автор технической книги SpringBoot Technology Insider, который любит изучать технологии и писать технические статьи.

Публичный аккаунт: "Новые горизонты программ", публичный аккаунт блогера, прошу обратить внимание~

Технический обмен: пожалуйста, свяжитесь с блогером WeChat ID: zhuan2quan