Java Academy · Урок

ScheduledExecutorService для повторяющихся задач

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

Урок 4 из 413 шагов

«ScheduledExecutorService для повторяющихся задач» — бесплатный урок Java Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.

Зачем нужен ScheduledExecutorService

ScheduledExecutorService расширяет ExecutorService, добавляя поддержку планирования задач. Он заменяет устаревшие классы Timer/TimerTask планировщиком на основе пула потоков, безопасно обрабатывающим исключения.

import java.util.concurrent.*;

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

// One-shot delay
scheduler.schedule(
    () -> System.out.println("Runs once after 3 seconds"),
    3, TimeUnit.SECONDS
);

// Don't forget to shut down eventually:
// scheduler.shutdown();

schedule: однократная задержка

schedule(task, delay, unit) запускает задачу один раз после указанной задержки. Работает как с задачами без результата, так и с задачами с результатом:

ScheduledExecutorService s = Executors.newScheduledThreadPool(1);

// Runnable:
ScheduledFuture<?> sf1 = s.schedule(
    () -> System.out.println("Runnable fired!"), 2, TimeUnit.SECONDS);

// Callable:
ScheduledFuture<Integer> sf2 = s.schedule(
    () -> { System.out.println("Callable fired!"); return 42; },
    2, TimeUnit.SECONDS);

System.out.println(sf2.get()); // 42 (after 2 seconds)

scheduleAtFixedRate

scheduleAtFixedRate(task, initialDelay, period, unit) многократно запускает задачу с фиксированной частотой. Если задача выполняется дольше указанного периода, следующий запуск начинается сразу после её завершения (перекрытия выполнений не происходит).

ScheduledExecutorService s = Executors.newScheduledThreadPool(1);
AtomicInteger count = new AtomicInteger();

ScheduledFuture<?> sf = s.scheduleAtFixedRate(
    () -> System.out.println("Tick " + count.incrementAndGet()),
    0,   // initial delay
    1,   // period
    TimeUnit.SECONDS
);

Thread.sleep(5000);
sf.cancel(false); // stop after 5 ticks
s.shutdown();

scheduleWithFixedDelay

scheduleWithFixedDelay(task, initialDelay, delay, unit) ждёт указанную задержку ПОСЛЕ завершения каждой задачи, прежде чем запустить следующую. Подходит для опроса, когда длительность задачи меняется.

s.scheduleWithFixedDelay(
    () -> {
        System.out.println("Polling... " + Instant.now());
        // variable-length polling task
    },
    0,  // no initial delay
    5,  // 5-second gap AFTER each completion
    TimeUnit.SECONDS
);
// Next execution = task end + 5 seconds (not task start)

Фиксированная частота и фиксированная задержка

Основное различие:

  • scheduleAtFixedRate: следующий запуск = последний запуск + период (время не смещается)
  • scheduleWithFixedDelay: следующий запуск = последнее завершение + задержка (между выполнениями гарантирован интервал)

Используйте fixedRate для сигналов активности, а fixedDelay — для опроса, когда длительность задачи меняется.

ScheduledFuture: отмена

schedule/scheduleAtFixedRate/scheduleWithFixedDelay возвращают объект ScheduledFuture. Вызовите cancel(false), чтобы остановить будущие запуски, не прерывая текущий:

ScheduledFuture<?> sf = scheduler.scheduleAtFixedRate(
    () -> System.out.println("Heartbeat"), 0, 1, TimeUnit.SECONDS);

// Cancel after 5 seconds
scheduler.schedule(() -> sf.cancel(false), 5, TimeUnit.SECONDS);

Обработка исключений в запланированных задачах

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

scheduler.scheduleAtFixedRate(() -> {
    try {
        // risky work
        if (Math.random() < 0.3) throw new RuntimeException("transient");
        System.out.println("Success");
    } catch (Exception e) {
        System.err.println("Task error: " + e.getMessage());
        // task continues to run on next interval
    }
}, 0, 2, TimeUnit.SECONDS);

Практический пример: обновление кэша

Используйте запланированную задачу для периодического обновления кэша:

class PriceCache {
    private volatile Map<String, Double> prices = new HashMap<>();
    
    PriceCache(ScheduledExecutorService scheduler) {
        scheduler.scheduleAtFixedRate(this::refresh, 0, 30, TimeUnit.SECONDS);
    }
    
    private void refresh() {
        System.out.println("Refreshing prices...");
        // prices = fetchFromDatabase();
    }
    
    Double getPrice(String symbol) { return prices.get(symbol); }
}

Практический пример: повтор с увеличением интервала

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

void retryWithBackoff(Runnable task, int maxRetries, ScheduledExecutorService s) {
    AtomicInteger attempts = new AtomicInteger(0);
    long[] delay = {1}; // seconds
    
    Runnable wrapper = new Runnable() {
        public void run() {
            try {
                task.run();
            } catch (Exception e) {
                int n = attempts.incrementAndGet();
                if (n < maxRetries) {
                    delay[0] *= 2;
                    s.schedule(this, delay[0], TimeUnit.SECONDS);
                } else System.err.println("Max retries reached");
            }
        }
    };
    s.execute(wrapper);
}

Мониторинг: getDelay и getQueue

Проверьте состояние планировщика:

ScheduledFuture<?> sf = scheduler.schedule(() -> {}, 10, TimeUnit.SECONDS);
System.out.println("Delay remaining: " + sf.getDelay(TimeUnit.SECONDS) + "s");

// ThreadPoolExecutor provides queue inspection:
if (scheduler instanceof ScheduledThreadPoolExecutor ste) {
    System.out.println("Queue size: " + ste.getQueue().size());
}

Завершение работы и очистка очереди

При завершении работы решите, должны ли запланированные задачи продолжать выполняться:

ScheduledThreadPoolExecutor ste = (ScheduledThreadPoolExecutor)
    Executors.newScheduledThreadPool(2);

// Cancel scheduled tasks on shutdown (default: false = they run)
ste.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
ste.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);

ste.shutdown();

Быстрая проверка

Задача, запланированная с фиксированным периодом запуска и задержкой в одну секунду, выполняется 3 секунды. Как планировщик обрабатывает такую ситуацию?

Итоги: ScheduledExecutorService

Главное:

  • schedule() — однократный запуск с задержкой
  • Запуск с фиксированным периодом — синхронизация по времени без смещения (от начала до начала)
  • Запуск с фиксированной задержкой — гарантированный промежуток (от завершения до начала)
  • Неперехваченные исключения молча останавливают повторяющиеся задачи — всегда заключайте код в try-catch
  • Отменяйте с помощью ScheduledFuture.cancel() с отключённым прерыванием
Можно начать бесплатно

Изучай Java с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
104
Уроки
374

Часто задаваемые вопросы

Урок «ScheduledExecutorService для повторяющихся задач» бесплатный?

Да — полный текст урока «ScheduledExecutorService для повторяющихся задач» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.

Чему я научусь в уроке «ScheduledExecutorService для повторяющихся задач»?

Планируйте задачи с фиксированной частотой или фиксированными задержками для опроса и фонового обслуживания. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Java Academy?

Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «ScheduledExecutorService для повторяющихся задач»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Java Academy?

Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. ExecutorService и типы пулов потоков
  2. Отправка задач: Runnable и Callable
  3. Future и обработка ошибок
  4. ScheduledExecutorService для повторяющихся задач
← Назад к Java Academy