Home » Java Timer и TimerTask — пример использования
Java Timer и TimerTask — пример использования

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 и не забывайте про мониторинг своих задач в продакшене.


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

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

Leave a reply

Your email address will not be published. Required fields are marked