Timer的使用

Java Timer 定时任务调度详解

Timer 是 Java 原生提供的定时任务调度工具,虽然功能相对简单,但在简单场景下可以满足定时执行任务的需求。本文将详细解析 Timer 的核心组件、调度方式及使用细节。

Timer 核心组件

Timer 框架主要由两个核心类构成:TimerTimerTask

TimerTask:任务载体

TimerTask 是一个抽象类,实现了 Runnable 接口,代表一个可以被 Timer 调度的任务。

public abstract class TimerTask implements Runnable {
    // 任务状态常量
    static final int VIRGIN = 0;       // 未调度
    static final int SCHEDULED = 1;    // 已调度(等待执行)
    static final int EXECUTED = 2;     // 已执行(非重复任务)
    static final int CANCELLED = 3;    // 已取消

    int state = VIRGIN;  // 初始状态为未调度

    // 抽象方法,需用户实现具体任务逻辑
    public abstract void run();

    // 取消任务
    public boolean cancel() { ... }

    // 获取任务下次执行时间
    public long scheduledExecutionTime() { ... }
}

注意:虽然 TimerTask 实现了 Runnable,但它并非独立线程,其 run 方法由 Timer 内部的线程调用(单线程执行)。

Timer:调度器

Timer 负责管理和调度 TimerTask,内部通过一个线程(TimerThread)执行所有任务,并使用优先级队列(TaskQueue)存储待执行任务。

核心功能:

  • 调度任务(一次性或周期性执行)。
  • 管理任务队列(添加、移除、排序)。
  • 控制任务执行线程的生命周期。

任务调度方式

Timer 提供了两种主要的调度方法:schedule(固定时延)和 scheduleAtFixedRate(固定速率),适用于不同场景。

1. schedule:固定时延调度

特点:任务的下次执行时间 = 上一次任务实际完成时间 + 间隔时间(不受任务执行耗时影响,会顺延)。

方法定义

// 延迟 delay 毫秒后执行一次
public void schedule(TimerTask task, long delay);

// 在指定时间执行一次
public void schedule(TimerTask task, Date time);

// 延迟 delay 毫秒后,每隔 period 毫秒重复执行
public void schedule(TimerTask task, long delay, long period);

// 在指定时间开始,每隔 period 毫秒重复执行
public void schedule(TimerTask task, Date firstTime, long period);

示例

Timer timer = new Timer();
// 延迟 1 秒后,每 2 秒执行一次
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("任务执行:" + new Date());
        try {
            Thread.sleep(1000); // 模拟任务耗时 1 秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}, 1000, 2000);

执行结果(时间间隔为上次完成 + 2 秒):

任务执行:10:00:01  // 首次执行(延迟 1 秒)
任务执行:10:00:04  // 10:00:01 + 1 秒(执行耗时) + 2 秒(间隔)= 10:00:04
任务执行:10:00:07  // 以此类推

2. scheduleAtFixedRate:固定速率调度

特点:任务的下次执行时间 = 上一次任务计划执行时间 + 间隔时间(尽可能保证固定频率,即使任务执行耗时超过间隔)。

方法定义

// 延迟 delay 毫秒后,以 period 为周期固定速率执行
public void scheduleAtFixedRate(TimerTask task, long delay, long period);

// 在指定时间开始,以 period 为周期固定速率执行
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period);

示例:(复用上面的任务代码,仅修改调度方法)

timer.scheduleAtFixedRate(new TimerTask() { ... }, 1000, 2000);

执行结果(时间间隔为上次计划时间 + 2 秒):

任务执行:10:00:01  // 首次执行(计划时间)
任务执行:10:00:03  // 计划时间 10:00:01 + 2 秒 = 10:00:03(即使任务耗时 1 秒,仍按计划时间执行)
任务执行:10:00:05  // 以此类推

注意:如果任务执行耗时超过周期,会导致任务 “追赶执行”(上一个未完成,下一个已触发,会在当前任务结束后立即执行)。

任务管理方法

1. cancel ():取消 Timer 所有任务

public void cancel() {
    synchronized(queue) {
        thread.newTasksMayBeScheduled = false; // 标记不再接受新任务
        queue.clear(); // 清空任务队列
        queue.notify(); // 唤醒等待的线程,使其退出
    }
}

作用

  • 终止 Timer 线程,不再执行任何任务(包括已调度但未执行的任务)。
  • 已执行的任务不受影响。

2. purge ():清理已取消的任务

public int purge() {
    int result = 0;
    synchronized(queue) {
        // 遍历队列,移除状态为 CANCELLED 的任务
        for (int i = queue.size(); i > 0; i--) {
            if (queue.get(i).state == TimerTask.CANCELLED) {
                queue.quickRemove(i);
                result++;
            }
        }
        if (result != 0) queue.heapify(); // 重新堆化队列
    }
    return result; // 返回清理的任务数量
}

作用:释放已取消任务占用的资源,优化队列性能。

Timer 的局限性

  1. 单线程执行:所有任务由 Timer 内部的单个线程执行,若某个任务耗时过长,会阻塞后续任务(即使到了执行时间)。
  2. 异常终止风险:若任务执行时抛出未捕获异常,Timer 线程会直接终止,导致所有后续任务失效。
  3. 时间精度低:依赖系统时间,且调度精度受线程调度影响(毫秒级,但实际可能有偏差)。
  4. 不支持复杂场景:如任务依赖、动态调整周期、分布式调度等,需依赖 Quartz、ScheduledExecutorService 等工具。

使用建议

  • 简单场景:可使用 Timer(如定时清理临时文件、简单的周期性任务)。
  • 复杂场景:推荐使用 ScheduledExecutorService(JUC 提供,支持多线程、异常处理更完善)或第三方框架(如 Quartz)。
  • 避免耗时任务:若必须使用 Timer,确保任务执行时间远小于调度周期,避免阻塞。