- Home »

Java Timer и TimerTask — пример использования
Если ты занимаешься серверной разработкой на Java, то наверняка сталкивался с задачами, где нужно выполнить что-то по расписанию. Очистить кэш, отправить отчёт, проверить состояние сервисов — всё это требует планировщика задач. И хотя в современном мире есть Spring Scheduler, Quartz и другие крутые решения, иногда нужно что-то простое и без лишних зависимостей. Именно для таких случаев в Java есть встроенные классы Timer и TimerTask, которые работают из коробки и не требуют дополнительных библиотек. Особенно это актуально, когда разворачиваешь приложение на VPS с ограниченными ресурсами и каждый мегабайт на счету.
Как это работает под капотом
Timer и TimerTask — это классическая связка из пакета java.util, которая появилась ещё в Java 1.3. Принцип работы довольно простой:
- Timer — это планировщик, который управляет очередью задач и выполняет их в отдельном потоке
- TimerTask — абстрактный класс, от которого наследуются конкретные задачи
- Все задачи выполняются последовательно в одном потоке (это важно помнить!)
- Timer использует бинарную кучу для хранения задач, отсортированных по времени выполнения
Под капотом Timer создаёт daemon-поток с именем “Timer-X”, где X — порядковый номер. Этот поток постоянно проверяет очередь задач и выполняет те, время которых пришло.
Пошаговая настройка и базовые примеры
Давайте разберём, как быстро создать и запустить простую задачу:
import java.util.Timer;
import java.util.TimerTask;
import java.util.Date;
public class SimpleTimerExample {
public static void main(String[] args) {
Timer timer = new Timer();
// Создаём задачу
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("Задача выполнена в: " + new Date());
}
};
// Запускаем задачу через 2 секунды
timer.schedule(task, 2000);
// Не забываем завершить timer
timer.cancel();
}
}
Для повторяющихся задач используем другой вариант schedule:
import java.util.Timer;
import java.util.TimerTask;
public class RepeatingTimerExample {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask repeatingTask = new TimerTask() {
private int counter = 0;
@Override
public void run() {
counter++;
System.out.println("Выполнение #" + counter + " в потоке: " +
Thread.currentThread().getName());
// Остановим после 5 выполнений
if (counter >= 5) {
timer.cancel();
}
}
};
// Запуск через 1 секунду, затем каждые 3 секунды
timer.schedule(repeatingTask, 1000, 3000);
}
}
Методы планирования и их особенности
У Timer есть несколько методов для планирования задач, каждый со своими нюансами:
Метод | Описание | Поведение при пропуске | Использование |
---|---|---|---|
schedule(task, delay) | Одноразовое выполнение | N/A | Простые отложенные задачи |
schedule(task, delay, period) | Повторяющееся выполнение | Пропускает пропущенные выполнения | Обычные периодические задачи |
scheduleAtFixedRate(task, delay, period) | Фиксированная частота | Пытается “наверстать” пропущенные | Критичные к времени задачи |
Вот практический пример разницы между schedule и scheduleAtFixedRate:
import java.util.Timer;
import java.util.TimerTask;
import java.util.Date;
public class ScheduleVsFixedRate {
public static void main(String[] args) {
Timer timer1 = new Timer("Schedule-Timer");
Timer timer2 = new Timer("FixedRate-Timer");
// Медленная задача, которая выполняется дольше интервала
TimerTask slowTask1 = new TimerTask() {
@Override
public void run() {
System.out.println("Schedule: " + new Date());
try {
Thread.sleep(3000); // Спим 3 секунды
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
TimerTask slowTask2 = new TimerTask() {
@Override
public void run() {
System.out.println("FixedRate: " + new Date());
try {
Thread.sleep(3000); // Спим 3 секунды
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// Интервал 1 секунда, но задача выполняется 3 секунды
timer1.schedule(slowTask1, 0, 1000);
timer2.scheduleAtFixedRate(slowTask2, 0, 1000);
}
}
Практические кейсы для серверной разработки
Теперь рассмотрим реальные сценарии использования Timer в серверных приложениях:
Мониторинг состояния сервера
import java.util.Timer;
import java.util.TimerTask;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
public class ServerMonitor {
private Timer monitorTimer;
private final MemoryMXBean memoryBean;
public ServerMonitor() {
this.memoryBean = ManagementFactory.getMemoryMXBean();
this.monitorTimer = new Timer("ServerMonitor", true);
}
public void startMonitoring() {
TimerTask monitorTask = new TimerTask() {
@Override
public void run() {
checkMemoryUsage();
checkDiskSpace();
checkSystemLoad();
}
};
// Проверяем каждые 30 секунд
monitorTimer.scheduleAtFixedRate(monitorTask, 0, 30000);
}
private void checkMemoryUsage() {
long used = memoryBean.getHeapMemoryUsage().getUsed();
long max = memoryBean.getHeapMemoryUsage().getMax();
double percentage = (double) used / max * 100;
if (percentage > 80) {
System.err.println("ВНИМАНИЕ: Использование памяти " +
String.format("%.1f", percentage) + "%");
}
}
private void checkDiskSpace() {
File root = new File("/");
long freeSpace = root.getFreeSpace();
long totalSpace = root.getTotalSpace();
double percentage = (double) freeSpace / totalSpace * 100;
if (percentage < 10) {
System.err.println("ВНИМАНИЕ: Свободное место на диске " +
String.format("%.1f", percentage) + "%");
}
}
private void checkSystemLoad() {
double load = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
if (load > 2.0) {
System.err.println("ВНИМАНИЕ: Высокая нагрузка системы: " + load);
}
}
public void stopMonitoring() {
if (monitorTimer != null) {
monitorTimer.cancel();
}
}
}
Очистка кэша и временных файлов
import java.util.Timer;
import java.util.TimerTask;
import java.io.File;
import java.util.concurrent.ConcurrentHashMap;
public class CacheCleanup {
private final Timer cleanupTimer;
private final ConcurrentHashMap cache;
private final String tempDir;
public CacheCleanup(String tempDir) {
this.cache = new ConcurrentHashMap<>();
this.tempDir = tempDir;
this.cleanupTimer = new Timer("CacheCleanup", true);
// Запускаем очистку каждые 5 минут
scheduleCleanup();
}
private void scheduleCleanup() {
TimerTask cleanupTask = new TimerTask() {
@Override
public void run() {
cleanExpiredCache();
cleanTempFiles();
}
};
cleanupTimer.scheduleAtFixedRate(cleanupTask, 0, 300000); // 5 минут
}
private void cleanExpiredCache() {
long currentTime = System.currentTimeMillis();
cache.entrySet().removeIf(entry -> {
if (currentTime - entry.getValue().timestamp > 3600000) { // 1 час
System.out.println("Удаляем из кэша: " + entry.getKey());
return true;
}
return false;
});
}
private void cleanTempFiles() {
File tempDirectory = new File(tempDir);
if (tempDirectory.exists()) {
File[] files = tempDirectory.listFiles();
if (files != null) {
long currentTime = System.currentTimeMillis();
for (File file : files) {
// Удаляем файлы старше 2 часов
if (currentTime - file.lastModified() > 7200000) {
if (file.delete()) {
System.out.println("Удалён временный файл: " + file.getName());
}
}
}
}
}
}
static class CacheEntry {
final Object data;
final long timestamp;
CacheEntry(Object data) {
this.data = data;
this.timestamp = System.currentTimeMillis();
}
}
}
Подводные камни и частые ошибки
При работе с Timer и TimerTask есть несколько важных моментов, о которых многие забывают:
Проблема с исключениями
// ПЛОХО: Необработанное исключение убьёт Timer
TimerTask badTask = new TimerTask() {
@Override
public void run() {
throw new RuntimeException("Упс!"); // Timer умрёт
}
};
// ХОРОШО: Обрабатываем исключения
TimerTask goodTask = new TimerTask() {
@Override
public void run() {
try {
// Основная логика
doSomethingRisky();
} catch (Exception e) {
System.err.println("Ошибка в задаче: " + e.getMessage());
e.printStackTrace();
}
}
private void doSomethingRisky() {
// Потенциально опасная операция
}
};
Проблема с завершением приложения
public class ProperTimerUsage {
private static Timer timer;
public static void main(String[] args) {
timer = new Timer();
// Регистрируем shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Завершаем Timer...");
if (timer != null) {
timer.cancel();
}
}));
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("Работаем...");
}
};
timer.schedule(task, 0, 1000);
}
}
Сравнение с альтернативными решениями
Решение | Плюсы | Минусы | Когда использовать |
---|---|---|---|
Timer/TimerTask | Простота, встроенность | Один поток, убивается исключениями | Простые задачи в небольших приложениях |
ScheduledExecutorService | Пул потоков, надёжность | Чуть сложнее в использовании | Современные Java-приложения |
Quartz | Мощность, кластеризация | Сложность, размер | Энтерпрайз-приложения |
Spring @Scheduled | Декларативность | Зависимость от Spring | Spring-приложения |
Современная альтернатива — ScheduledExecutorService
Для сравнения покажу, как та же задача выглядит с использованием более современного подхода:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ModernSchedulingExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
// Аналог Timer.schedule()
executor.schedule(() -> {
System.out.println("Одноразовая задача");
}, 2, TimeUnit.SECONDS);
// Аналог Timer.scheduleAtFixedRate()
executor.scheduleAtFixedRate(() -> {
System.out.println("Повторяющаяся задача");
}, 0, 3, TimeUnit.SECONDS);
// Корректное завершение
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
executor.shutdown();
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}));
}
}
Интересные факты и нестандартные применения
Вот несколько интересных способов использования Timer, которые не сразу приходят в голову:
Таймаут для операций
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
public class TimeoutExample {
public static void longRunningOperationWithTimeout() {
Timer timeoutTimer = new Timer();
AtomicBoolean completed = new AtomicBoolean(false);
TimerTask timeoutTask = new TimerTask() {
@Override
public void run() {
if (!completed.get()) {
System.err.println("Операция превысила таймаут!");
// Прерываем операцию
Thread.currentThread().interrupt();
}
}
};
timeoutTimer.schedule(timeoutTask, 5000); // 5 секунд таймаут
try {
// Долгая операция
Thread.sleep(10000);
completed.set(true);
} catch (InterruptedException e) {
System.out.println("Операция прервана по таймауту");
} finally {
timeoutTimer.cancel();
}
}
}
Периодическое сохранение состояния
import java.util.Timer;
import java.util.TimerTask;
import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;
public class StatePreservation {
private final AtomicLong counter = new AtomicLong(0);
private final Timer saveTimer;
private final String stateFile;
public StatePreservation(String stateFile) {
this.stateFile = stateFile;
this.saveTimer = new Timer("StateSaver", true);
// Сохраняем состояние каждые 10 секунд
scheduleStateSaving();
}
private void scheduleStateSaving() {
TimerTask saveTask = new TimerTask() {
@Override
public void run() {
saveState();
}
};
saveTimer.scheduleAtFixedRate(saveTask, 10000, 10000);
}
private void saveState() {
try (FileWriter writer = new FileWriter(stateFile)) {
writer.write(String.valueOf(counter.get()));
System.out.println("Состояние сохранено: " + counter.get());
} catch (IOException e) {
System.err.println("Ошибка сохранения состояния: " + e.getMessage());
}
}
public void incrementCounter() {
counter.incrementAndGet();
}
}
Автоматизация и интеграция со скриптами
Timer отлично подходит для создания Java-приложений, которые автоматизируют системные задачи. Особенно это полезно на выделенных серверах, где нужно следить за множеством параметров:
import java.util.Timer;
import java.util.TimerTask;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class SystemTaskAutomation {
private Timer systemTimer;
public void startAutomation() {
systemTimer = new Timer("SystemAutomation", true);
// Ротация логов каждый день в 2:00
scheduleLogRotation();
// Проверка обновлений каждые 6 часов
scheduleUpdateCheck();
// Backup базы данных каждые 12 часов
scheduleBackup();
}
private void scheduleLogRotation() {
TimerTask logRotationTask = new TimerTask() {
@Override
public void run() {
executeSystemCommand("logrotate /etc/logrotate.conf");
}
};
// Запускаем каждые 24 часа
systemTimer.scheduleAtFixedRate(logRotationTask, 0, 86400000);
}
private void scheduleUpdateCheck() {
TimerTask updateTask = new TimerTask() {
@Override
public void run() {
executeSystemCommand("apt list --upgradable");
}
};
systemTimer.scheduleAtFixedRate(updateTask, 0, 21600000); // 6 часов
}
private void scheduleBackup() {
TimerTask backupTask = new TimerTask() {
@Override
public void run() {
String timestamp = String.valueOf(System.currentTimeMillis());
String command = "mysqldump -u root -p database > /backup/db_" + timestamp + ".sql";
executeSystemCommand(command);
}
};
systemTimer.scheduleAtFixedRate(backupTask, 0, 43200000); // 12 часов
}
private void executeSystemCommand(String command) {
try {
Process process = Runtime.getRuntime().exec(command);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
int exitCode = process.waitFor();
System.out.println("Команда завершена с кодом: " + exitCode);
} catch (Exception e) {
System.err.println("Ошибка выполнения команды: " + e.getMessage());
}
}
}
Производительность и ограничения
Важно понимать ограничения Timer для принятия правильных архитектурных решений:
- Один поток — все задачи выполняются последовательно
- Блокировка — долгая задача блокирует все остальные
- Хрупкость — необработанное исключение убивает весь Timer
- Точность — зависит от системных часов, может “дрейфовать”
Для тестирования производительности можно использовать такой код:
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicLong;
public class TimerPerformanceTest {
public static void main(String[] args) {
Timer timer = new Timer();
AtomicLong counter = new AtomicLong(0);
long startTime = System.currentTimeMillis();
TimerTask task = new TimerTask() {
@Override
public void run() {
counter.incrementAndGet();
// Останавливаем после 10 секунд
if (System.currentTimeMillis() - startTime > 10000) {
timer.cancel();
System.out.println("Выполнено задач: " + counter.get());
System.out.println("Задач в секунду: " + counter.get() / 10);
}
}
};
// Запускаем каждые 10 мс
timer.scheduleAtFixedRate(task, 0, 10);
}
}
Выводы и рекомендации
Timer и TimerTask — это проверенные временем инструменты, которые отлично подходят для решения простых задач планирования. Вот когда их стоит использовать:
- Простые периодические задачи — очистка кэша, мониторинг, логирование
- Легковесные приложения — когда не хочется тащить дополнительные зависимости
- Быстрые прототипы — для проверки идей и концептов
- Системные утилиты — автоматизация рутинных задач администрирования
Избегайте Timer в следующих случаях:
- Нужна высокая надёжность и отказоустойчивость
- Требуется выполнение задач в нескольких потоках
- Критична точность выполнения по времени
- Планируется сложная логика управления задачами
Для современных приложений лучше использовать ScheduledExecutorService или специализированные решения вроде Quartz. Но Timer остаётся отличным выбором для быстрых решений и обучения основам планирования задач в Java.
Помните: любой планировщик задач — это мощный инструмент, который может как значительно упростить жизнь, так и создать головную боль при неправильном использовании. Всегда обрабатывайте исключения, правильно завершайте Timer и не забывайте про мониторинг своих задач в продакшене.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.